• 单链表的头插法和尾插法的示例


      单链表是数据结构中最基本的数据结构,单链表有头插法和尾插法,今天有空把这两者做成一个实验示例以作比较。

     

      提示:编译代码是否通过可能与编译器的版本有关,本程序是在 Android 操作系统下的 Compiler C语言编译器下编译通过。

     

      一、头插法和尾插法创建单链表

      增、删、改、查的算法只写了增、查两个,其它省略。

    1. /*
    2. 程序功能:单链表的头插法和尾插法实验示例
    3. 说明:本程序的增删改查功能部分实现
    4. 作者:九天一声啸
    5. 邮箱:946585434@qq.com
    6. 日期:2022.09.09
    7. */
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #define BOOL int
    14. #define TRUE 1
    15. #define OK 1
    16. #define FALSE 0
    17. #define NO 0
    18. #define ERROR -1
    19. //链表长度
    20. int length = 16;
    21. //TRUE 为自然数列,FALSE 为随机数
    22. bool choose = true;
    23. //bool choose = false;
    24. //定义结构体
    25. typedef struct Node
    26. {
    27. int data;
    28. struct Node *next;
    29. }Node, *LinkList;
    30. LinkList head = NULL;
    31. LinkList end = NULL; //尾插法时使用的结构体指针
    32. //头插法函数
    33. void addHead(int data)
    34. {
    35. LinkList L;
    36. L = (LinkList)malloc(sizeof(Node));
    37. L->data = data;
    38. L->next = head->next;
    39. head->next = L;
    40. }
    41. //尾插法函数
    42. void addEnd(int data) {
    43. LinkList L;
    44. L = (LinkList)malloc(sizeof(Node));
    45. L->next = NULL;
    46. L->data = data;
    47. if(NULL == head)
    48. {
    49. head = L;
    50. } else {
    51. end->next = L;
    52. }
    53. end = L;
    54. }
    55. //头插法遍历链表并输出
    56. void showListHead()
    57. {
    58. LinkList tmp;
    59. tmp = head->next;
    60. while (tmp != NULL)
    61. {
    62. printf ("%d ", tmp->data);
    63. tmp = tmp->next;
    64. }
    65. }
    66. //尾插法遍历链表并输出
    67. void showListEnd()
    68. {
    69. LinkList tmp;
    70. tmp = head;
    71. while(tmp)
    72. {
    73. printf("%d ", tmp->data);
    74. tmp = tmp->next;
    75. }
    76. }
    77. int main()
    78. {
    79. int i;
    80. char str[10];
    81. BOOL tag;
    82. srand(time(0));
    83. printf("请输入内容,true 是尾插法;false 是头插法:\n\t->");
    84. scanf("%s",&str);
    85. /*
    86. 判断输入内容
    87. tag = 1 为true;
    88. tag = 0 为false
    89. tag = -1 为error
    90. */
    91. if(strcmp(str, "true") == 0)
    92. tag = TRUE;
    93. else if(strcmp(str, "false") == 0)
    94. tag = FALSE;
    95. else
    96. tag = ERROR;
    97. if(tag == TRUE) {
    98. //头插法需要这段代码,否则错误
    99. head = (LinkList)malloc(sizeof(Node));
    100. head->next = NULL;
    101. //头插法插入数据
    102. for(i = 0; i < length; i++)
    103. {
    104. if(choose == true)
    105. addHead(i);
    106. else
    107. //随机数
    108. addHead(rand()%100 + 1);
    109. }
    110. printf("头插法:\n");
    111. //头插法显示
    112. showListHead();
    113. } else if(tag == FALSE) {
    114. //尾插法插入数据
    115. for(i = 0; i < length; i++)
    116. {
    117. if(choose == true)
    118. addEnd(i);
    119. else
    120. //随机数
    121. addEnd(rand()%100 + 1);
    122. }
    123. printf("尾插法:\n");
    124. //尾插法显示
    125. showListEnd();
    126. } else {
    127. printf("输入错误!");
    128. }
    129. return 0;
    130. }
    wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

     

    程序执行后的效果:

    3711f63a522247fb9bc6b634387421a5.png

     

     

      二、完整的单链表数据结构如下:

      在实际应用中,结构体中 data 的数据类型应该是另一个结构体数据类型,即 data 代表数据元素,用于定义姓名、姓别、年龄、地址等等数据项。

    1. /*
    2. 程序功能:单链表的头插法和尾插法实验示例
    3. 说明:本程序的增删改查功能全部实现
    4. 作者:九天一声啸
    5. 邮箱:946585434@qq.com
    6. 日期:2022.09.09
    7. */
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. //自定义布尔类型
    14. #define BOOL int
    15. #define TRUE 1
    16. #define OK 1
    17. #define FALSE 0
    18. #define ERROR -1
    19. BOOL tag;
    20. //自定义数据类型 ElemType
    21. #define ElemType int
    22. //链表长度
    23. int length = 16;
    24. /*
    25. true 为自然数列,false 为随机数
    26. */
    27. //bool choose = true;
    28. bool choose = false;
    29. //定义结构体
    30. typedef struct Node{
    31. /*
    32. data 代表的是数据元素,
    33. 实际应用中 data 应该是另一个结构体,
    34. 并且在结构体中定义数据项。
    35. */
    36. int data; //结点数据
    37. struct Node *next; //结点指针
    38. }Node, *LinkList;
    39. LinkList head = NULL;
    40. LinkList end = NULL; //尾插法时使用的结构体指针
    41. /*
    42. 功能:头插法算法函数
    43. @param int data 链表数据
    44. @return void
    45. */
    46. void addHead(int data){
    47. LinkList L;
    48. //头插法核心代码 begin
    49. if(NULL == head)
    50. {
    51. //如果没有头节点,创建头节点
    52. head = (LinkList)malloc(sizeof(Node));
    53. head->next = NULL;
    54. }
    55. else
    56. {
    57. L = (LinkList)malloc(sizeof(Node));
    58. L->data = data;
    59. L->next = head->next;
    60. head->next = L;
    61. }
    62. //头插法核心代码 end.
    63. }
    64. /*
    65. 功能:尾插法算法函数
    66. @param int data 链表数据
    67. @return void
    68. */
    69. void addEnd(ElemType data){
    70. LinkList L;
    71. //尾插法核心代码 begin
    72. L = (LinkList)malloc(sizeof(Node));
    73. L->data = data;
    74. L->next = NULL;
    75. if(NULL == head)
    76. {
    77. //如果没有头节点,创建头节点
    78. head = (LinkList)malloc(sizeof(Node));
    79. head->next = L;
    80. }else{
    81. end->next = L;
    82. }
    83. end = L;
    84. //尾插法核心代码 end.
    85. }
    86. /*
    87. 功能:返回头结点指针
    88. @return LinkList 自定义结构体指针类型
    89. */
    90. LinkList getLinkNode()
    91. {
    92. LinkList L;
    93. L = head->next;
    94. return L;
    95. }
    96. /*
    97. 功能:根据输入链表的位置查询链表数据
    98. @param int num 待查询链表的位置
    99. @return int
    100. */
    101. int getElem(int num)
    102. {
    103. LinkList L;
    104. int i;
    105. /*
    106. 由于程序是从 0 开始计数的,见 linkCreate() 函数内容,
    107. 我们数数时是从 1 开始的,为了让认知一致,i 应该从 1 开始
    108. 计数。
    109. */
    110. i = 1;
    111. L = getLinkNode();
    112. //循环移动指针到指定位置
    113. while(L && i < num)
    114. {
    115. //指针向后移动一位
    116. L = L->next;
    117. ++i;
    118. }
    119. if(!L || i > num)
    120. return ERROR;
    121. //查询节点数据的核心代码,直接返回数据即可
    122. return L->data;
    123. }
    124. /*
    125. 功能:根据数据位置修改链表
    126. @param int num 待查询链表的位置
    127. @param int data 待修改的数据
    128. @return BOOL 自定义的布尔类型
    129. */
    130. BOOL changeElem(int num, ElemType data)
    131. {
    132. LinkList L;
    133. int i;
    134. i = 1;
    135. L = getLinkNode();
    136. //循环移动指针到指定位置
    137. while(L && i < num)
    138. {
    139. //指针向后移动一位
    140. L = L->next;
    141. ++i;
    142. }
    143. if(!L || i > num)
    144. return ERROR;
    145. //修改节点数据的核心代码 begin
    146. L->data = data;
    147. //修改节点数据的核心代码 end
    148. return OK;
    149. }
    150. /*
    151. 功能:根据输入链表的位置插入链表节点
    152. @param int num 待插入链表的位置
    153. @param int data 待插入的数据
    154. @return BOOL 自定义的布尔类型
    155. */
    156. BOOL insertList(int num, ElemType data)
    157. {
    158. LinkList L, s;
    159. int j;
    160. j = 1;
    161. L = getLinkNode();
    162. //循环移动指针到指定位置
    163. while(L && j < num)
    164. {
    165. //指针向后移动一个节点
    166. L = L->next;
    167. ++j;
    168. }
    169. if(!L || j > num)
    170. return ERROR;
    171. //插入节点和数据的核心代码 begin
    172. s = (LinkList)malloc(sizeof(Node));
    173. s->data = data;
    174. s->next = L->next;
    175. L->next = s;
    176. //插入节点和数据的核心代码 end
    177. return OK;
    178. }
    179. /*
    180. 功能:根据输入链表的位置删除链表的节点
    181. @param int num 要删除链表的位置
    182. @return ElemType 自定义类型
    183. */
    184. ElemType deleteList(int num)
    185. {
    186. LinkList L, s;
    187. ElemType tmp;
    188. int j;
    189. /*
    190. 由于程序是从0开始计数的,见 linkCreate() 函数内容,
    191. 我们数数时是从1开始的,为了让认知一致,j 应该从 1 开始
    192. 计数;
    193. 再由于,要删除的数据是从查询到的位置的下一个位置开始删
    194. 的,所以这里 j 再加1,即 j = 2。
    195. */
    196. j = 2;
    197. /*
    198. 双向链表有前驱指针可以在删除第一个节点时,直接指向头节点。然而,
    199. 由于单向链表没有前驱指针,在指向头节点后的第一个节点时,在删除
    200. 第一个节点时删除不了第一个节点,还是删除的是第二个节点,因此
    201. 这里如果要删除第一个节点的话,要把指针指向头节点。
    202. */
    203. if (num == 1)
    204. /*
    205. 删除第一个节点时,指针指向头节点,头节点没有数据内容,
    206. 即 head->data 为空。
    207. */
    208. L = head;
    209. else
    210. /*
    211. 删除其它节点时,指针指向头节点后有数据内容的第一个节点,
    212. 即 head->next->data 是 head 头节点后,第一个节点的数据。
    213. 注意:head->next 是 head 节点后的第一个节点。
    214. */
    215. L = getLinkNode();
    216. //循环移动指针到指定位置
    217. while(L && j < num)
    218. {
    219. //指针向后移动一个节点
    220. L = L->next;
    221. j++;
    222. }
    223. if((!L || j > num) && num != 1)
    224. return ERROR;
    225. //删除节点的核心代码 begin
    226. s = L->next ;
    227. L->next = s->next ;
    228. tmp = s->data;
    229. free(s);
    230. //删除节点的核心代码 end
    231. return tmp;
    232. }
    233. /*
    234. 功能:链表的整表删除
    235. @return BOOL 自定义布尔类型
    236. */
    237. BOOL allDeletList()
    238. {
    239. LinkList L, s;
    240. s = head->next;
    241. //整表删除的核心代码 begin
    242. while(s)
    243. {
    244. L = s->next;
    245. free(s);
    246. s =L;
    247. }
    248. //整表删除的核心代码 end
    249. //TRUE:头插法;FALSE:尾插法
    250. if(tag == TRUE)
    251. head->next = NULL;
    252. else
    253. head = NULL;
    254. return OK;
    255. }
    256. //----------- 以上是单链表的算法实现
    257. //----------- 以下是辅助函数和程序,不必深究。
    258. /*
    259. 功能:头插法遍历链表并输出
    260. @return void
    261. */
    262. void showListHead()
    263. {
    264. LinkList tmp;
    265. tmp = head->next;
    266. while (tmp != NULL)
    267. {
    268. printf ("%d ", tmp->data);
    269. tmp = tmp->next;
    270. }
    271. }
    272. /*
    273. 功能:尾插法遍历链表并输出
    274. @return void
    275. */
    276. void showListEnd()
    277. {
    278. LinkList tmp;
    279. bool flag = false ;
    280. tmp = head;
    281. while(tmp != NULL)
    282. {
    283. if (flag)
    284. printf("%d ", tmp->data);
    285. else
    286. flag = true;
    287. tmp = tmp->next;
    288. }
    289. }
    290. /*
    291. 功能:遍历单链表并显示
    292. @return void
    293. */
    294. void showList()
    295. {
    296. //TRUE:头插法;FALSE:尾插法
    297. if(tag == TRUE)
    298. {
    299. //头插法遍历显示
    300. showListHead();
    301. }
    302. else if(tag == FALSE)
    303. {
    304. //尾插法遍历显示
    305. showListEnd();
    306. }
    307. }
    308. /*
    309. 功能:创建单链表
    310. @return BOOL 自定义的布尔类型
    311. */
    312. BOOL linkCreate()
    313. {
    314. int i;
    315. char str[10];
    316. srand(time(0));
    317. printf("1.请输入内容,true 是头插法;false 是尾插法:\n\t-> ");
    318. scanf("%s",&str);
    319. /*
    320. 判断输入内容
    321. tag = 1 为true;
    322. tag = 0 为false
    323. tag = -1 为error
    324. */
    325. if(strcmp(str, "true") == 0)
    326. tag = TRUE;
    327. else if(strcmp(str, "false") == 0)
    328. tag = FALSE;
    329. else
    330. tag = ERROR;
    331. //TRUE:头插法;FALSE:尾插法;ERROR:错误
    332. if(tag == TRUE){
    333. //头插法插入数据
    334. for(i = 0; i < length; ++i)
    335. {
    336. if(choose == TRUE)
    337. addHead(i);
    338. else
    339. //随机数
    340. addHead(rand()%100 + 1);
    341. }
    342. printf("头插法:\n");
    343. //头插法遍历显示
    344. showListHead();
    345. return OK;
    346. }else if(tag == FALSE){
    347. //尾插法插入数据
    348. for(i = 0; i < length; i++)
    349. {
    350. if(choose == TRUE)
    351. addEnd(i);
    352. else
    353. //随机数
    354. addEnd(rand()%100 + 1);
    355. }
    356. printf("尾插法:\n");
    357. //尾插法遍历显示
    358. showListEnd();
    359. return OK;
    360. }else{
    361. printf("输入错误!");
    362. return ERROR;
    363. }
    364. }
    365. /*
    366. 功能:根据输入链表的位置查询数据
    367. @return void
    368. */
    369. void linkQuery()
    370. {
    371. int input;
    372. printf("\n\n2.请输入要查询的位置:\n\t-> ");
    373. scanf("%d", &input);
    374. if(input <= length)
    375. {
    376. int getNum = getElem(input);
    377. printf("\n获取到的值:%d", getNum);
    378. }else{
    379. printf("\n╳ 没有这么长的链表!");
    380. }
    381. }
    382. /*
    383. 功能:根据输入链表的位置修改数据
    384. @return void
    385. */
    386. void linkRevise()
    387. {
    388. int input, data;
    389. printf("\n\n3.请输入要修改的位置和数据,两个数据用空格隔开:\n\t-> ");
    390. scanf("%d%d", &input, &data);
    391. if(input <= length)
    392. {
    393. changeElem(input, data);
    394. printf("\n链表修改后的值:\n");
    395. showList();
    396. }else{
    397. printf("\n╳ 没有这么长的链表!");
    398. }
    399. }
    400. /*
    401. 功能:根据输入链表的位置插入数据
    402. @return void
    403. */
    404. void linkInsert()
    405. {
    406. int input, data;
    407. printf("\n\n4.请输入要插入的位置和数据,两个数据用空格隔开:\n\t-> ");
    408. scanf("%d%d", &input, &data);
    409. if(input <= length)
    410. {
    411. int ret = insertList(input, data);
    412. if(ret == OK)
    413. length++;
    414. printf("\n链表修改后的值:\n");
    415. showList();
    416. }else{
    417. printf("\n╳ 没有这么长的链表!");
    418. }
    419. }
    420. /*
    421. 功能:根据输入链表的位置删除数据
    422. @return void
    423. */
    424. void linkDelete()
    425. {
    426. int input, data, tmp;
    427. printf("\n\n5.请输入要删除的位置:\n\t-> ");
    428. scanf("%d", &input);
    429. if(input <= length)
    430. {
    431. tmp = deleteList(input);
    432. if(tmp != ERROR)
    433. length--;
    434. printf("\n->被删除的值:%d\n\n", tmp);
    435. printf("\n链表删除后的值:\n");
    436. showList();
    437. }else{
    438. printf("\n╳ 没有这么长的链表!");
    439. }
    440. }
    441. /*
    442. 功能:主函数入口
    443. @return int
    444. */
    445. int main()
    446. {
    447. //链表操作:创建链表
    448. BOOL val = linkCreate();
    449. if(val != ERROR)
    450. {
    451. printf("\n--------------------------------\n");
    452. //链表查询
    453. linkQuery();
    454. printf("\n--------------------------------\n");
    455. //链表修改
    456. linkRevise();
    457. printf("\n--------------------------------\n");
    458. //链表插入
    459. linkInsert();
    460. printf("\n--------------------------------\n");
    461. //链表删除
    462. linkDelete();
    463. printf("\n--------------------------------\n");
    464. //整表删除
    465. allDeletList();
    466. printf("\n6.整表删除后的链表数据:");
    467. showList();
    468. }
    469. return 0;
    470. }
    wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

     

    程序执行后的效果:

    ac3100ce8fb443ed82268c203f1684a7.jpg

     

     

    三、结构体定义的完整的数据结构演示如下:

      把结构体的数据域写全,并用最原始的方法来演示一下单链表。

    1. #include
    2. #include
    3. #include
    4. #include
    5. //结构体定义的数据域
    6. typedef struct myData{
    7. int id;
    8. char name[20];
    9. short int sex; //0为男;1为女
    10. char address[100];
    11. }myData;
    12. //结构体定义的节点
    13. typedef struct Node{
    14. //数据域,这里用结构体变量,不能直接用指针,
    15. //如果没有变量,指针就不能指向变量的地址。
    16. struct myData data;
    17. //指针域
    18. struct Node *next;
    19. }Node, *LinkList;
    20. //性别判断
    21. void determine(short int flag, char* e)
    22. {
    23. if(flag == 0)
    24. strcpy(e, "男");
    25. else if(flag == 1)
    26. strcpy(e, "女");
    27. else
    28. strcpy(e, "未知");
    29. }
    30. //主函数入口
    31. int main()
    32. {
    33. char ssex[10] ="未知";
    34. //1.结构体变量,静态分配内存
    35. Node link1;
    36. //赋值
    37. link1.data.id = 12;
    38. strcpy(link1.data.name, "孙悟空");
    39. link1.data.sex = 0;
    40. strcpy(link1.data.address, "花果山水帘洞");
    41. //性别判断
    42. determine(link1.data.sex, ssex);
    43. printf("编号:%d\n姓名:%s\n姓别:%s\n地址:%s\n\n", link1.data.id, link1.data.name, ssex, link1.data.address);
    44. //2.malloc() 函数动态分配内存
    45. LinkList link2 = (LinkList)malloc(sizeof(Node));
    46. //赋值
    47. link2->data.id = 13;
    48. strcpy(link2->data.name, "嫦娥");
    49. link2->data.sex = 1;
    50. strcpy(link2->data.address, "月亮广寒宫");
    51. //性别判断
    52. determine(link2->data.sex, ssex);
    53. printf("编号:%d\n姓名:%s\n姓别:%s\n地址:%s\n\n", link2->data.id, link2->data.name, ssex, link2->data.address);
    54. //3.如果把上面的两个结构体变量组合成一个单向链表
    55. /*
    56. 用这种最原始的方法,才能看清楚它们的关系
    57. 这里 link1 是结构体变量,
    58. 而 link2 是 malloc() 直接返回的就是指针,非常方便。
    59. */
    60. Node L; //内存分配一个头节点
    61. Node* head = &L;
    62. //这里等效于:Node* head = (Node*)malloc(sizeof(Node));
    63. head->next = &link1;
    64. head->next->next = link2;
    65. head->next->next->next = NULL; //单链表要求尾节点的 next 为空
    66. /*
    67. 单链表生成完毕,这是最直观的单链表了,上面的五段代码看明白了,
    68. 单链表的算法已就能写出来了。
    69. 说明:
    70. 1.一般要求头节点除了 next 指针域是有效数据,data 数据域是无效数据,不用管它;
    71. 2.也可以在头节点放置链表长度,这时头节点要单独定义一个结构体,指针域不变,数据
    72. 域为:int length; 只需把 next 指向其它有数据的节点即可。这样的好处就是在
    73. 获取单链表的长度时,不用遍历整个链表,也就是时间复杂度由 O(n) 变为了 O(1),
    74. 但是,也可以不需要头节点。
    75. 3.一个 next 其实就是一个有数据内容的节点,而最后的尾节点为 NULL 而已。
    76. */
    77. printf("以下内容是从单链表输出的:\n\n");
    78. //显示第一个有数据的节点内容
    79. determine(head->next->data.sex, ssex);
    80. printf("编号:%d\n姓名:%s\n姓别:%s\n地址:%s\n\n", head->next->data.id, head->next->data.name, ssex, head->next->data.address);
    81. //显示第二个有数据的节点内容
    82. determine(head->next->next->data.sex, ssex);
    83. printf("编号:%d\n姓名:%s\n姓别:%s\n地址:%s\n", head->next->next->data.id, head->next->next->data.name, ssex, head->next->next->data.address);
    84. return 0;
    85. }
    wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

     

      程序执行后的效果:

    3d80c9c3ca07421ca15bd83ec0412c8c.jpg

     

     

     

     

  • 相关阅读:
    医院如何实现安全又稳定的跨网文件数据交换呢?
    前端传递bool型后端用int收不到
    刚刚!苹果发布Apple Intelligence,官宣免费接入ChatGPT,Siri迎来重磅更新
    2023年【安全生产监管人员】考试题及安全生产监管人员找解析
    总结——》【Redis】
    7z命令行
    Golang 字符串
    【golang】go app 优雅关机 Graceful Shutdown How?
    【Unity ShaderGraph】| Shader Graph入门介绍 | 简介 | 配置环境 | 窗口介绍 | 简单案例
    tcpdump工具使用
  • 原文地址:https://blog.csdn.net/dai510131/article/details/126790186