一、自己学习链表

数组的缺点:(1)数据类型一致;(2)数组的长度事先定好,不能灵活更改。

从而引入了链表来解决数组的这些缺点:(1)结构体解决多数据类型(2)链表的组合使得链表的长度可以灵活设置。

基本概念:

头结点:

这个节点是为了便于管理链表的节点,这个节点并不保存数据;虽然和其他节点一样,但是这个头结点是指向首节点的节点。

首节点:

第一个保存有效数据的节。

尾节点:

最后一个保存有效数据的节点

头指针:

头指针是指向头节点的指针。


单链表:

链表节点的数据结构定义:

  1. typedef struct Node
  2. {
  3. int data;
  4. struct Node *PNEXT;
  5. }NODE,*PNODE;

单链表的代码:

  1.  
  2. typedef struct Node
  3. {
  4. int data;
  5. struct Node *PNEXT;
  6. }NODE,*PNODE;
  7.  
  8. // 创建链表的节点
  9. PNODE create_node(int data)
  10. {
  11. // 创建结点
  12. PNODE p = (PNODE)malloc(sizeof(NODE));
  13. if (NULL == p)
  14. {
  15. printf("malloc error\n");
  16. exit(-);
  17. }
  18. memset(p, , sizeof(NODE));
  19.  
  20. p->data = data;
  21. p->PNEXT = NULL;
  22.  
  23. return p;
  24. }
  25.  
  26. // 链表的初始化
  27. PNODE create_list()
  28. {
  29.  
  30. int iLen_list = ;
  31. int iData_list = ;
  32.  
  33. // 创建头结点
  34. PNODE pHead = NULL;
  35. pHead = (PNODE)malloc(sizeof(NODE));
  36. if (NULL == pHead)
  37. {
  38. printf("malloc pHead error\n");
  39. exit(-);
  40. }
  41. pHead->data = NULL;
  42. pHead->PNEXT = NULL;
  43.  
  44. // 定义一个尾指针
  45. PNODE pTail = pHead;
  46.  
  47. printf("输出链表的长度,n = \n");
  48. scanf("%d", &iLen_list);
  49.  
  50. // 初始化创建节点的数据
  51. for (size_t i = ; i < iLen_list; i++)
  52. {
  53. PNODE pNew = NULL;
  54. printf("输出链表第 %d 个的值", i + );
  55. scanf("%d", &iData_list);
  56.  
  57. // 创建节点
  58. pNew = create_node(iData_list);
  59.  
  60. // 保证尾指针是一直指向最后一个节点
  61. pTail->PNEXT = pNew;
  62. pTail = pNew;
  63. }
  64.  
  65. return pHead;
  66. }
  67.  
  68. // 判断链表是否为空
  69. bool is_list_empty(PNODE pHead)
  70. {
  71. if (pHead->PNEXT == nullptr)
  72. return true;
  73. else
  74. return false;
  75. }
  76.  
  77. // 链表的长度
  78. int len_list(PNODE pHead)
  79. {
  80. PNODE pNew = pHead;
  81. int i = ;
  82.  
  83. while ( pNew->PNEXT != nullptr )
  84. {
  85. i++;
  86. pNew = pNew->PNEXT;
  87. }
  88. return i;
  89. }
  90.  
  91. // 遍历链表的所有节点
  92. void traver_all_list(PNODE pHead)
  93. {
  94.  
  95. if (is_list_empty(pHead))
  96. {
  97. printf("空链表\n");
  98. exit(-);
  99. }
  100.  
  101. PNODE pNew = pHead;
  102. while (pNew->PNEXT != nullptr )
  103. {
  104. pNew = pNew->PNEXT;
  105. printf("%d\n", pNew->data);
  106. }
  107. }
  108.  
  109. // 链表的尾部添加数据
  110. bool list_append_tail(PNODE pHead,int data)
  111. {
  112.  
  113. int i = NULL;
  114. if (is_list_empty(pHead))
  115. {
  116. printf("空链表\n");
  117. return false;
  118. }
  119.  
  120. PNODE pNew = pHead;
  121. while ((pNew->PNEXT != nullptr) )
  122. { // pNew 最后指向最后一个节点
  123. pNew = pNew->PNEXT;
  124. }
  125.  
  126. PNODE pNewOne = create_node(data);
  127. pNew->PNEXT = pNewOne;
  128.  
  129. pNewOne->data = data;
  130. pNewOne->PNEXT = nullptr;
  131.  
  132. return true;
  133. }
  134.  
  135. // 链表的尾部插入数据
  136. bool list_insert_tail(PNODE pHead, int data)
  137. {
  138.  
  139. int i = NULL;
  140. if (is_list_empty(pHead))
  141. {
  142. printf("空链表\n");
  143. return false;
  144. }
  145.  
  146. PNODE pNew = pHead;
  147. while ( (pNew->PNEXT != nullptr) && ( i<len_list(pHead) - ) )
  148. { // pNew 最后指向最后一个节点
  149. pNew = pNew->PNEXT;
  150. i++;
  151. }
  152.  
  153. PNODE pNewOne = create_node(data);
  154. pNewOne->PNEXT = pNew->PNEXT;
  155. pNewOne->data = data;
  156.  
  157. pNew->PNEXT = pNewOne;
  158.  
  159. return true;
  160. }
  161.  
  162. // 链表的头也就是添加一个新的链表的首节点
  163. bool list_insert_head(PNODE pHead, int data)
  164. {
  165. PNODE pNew = pHead;
  166.  
  167. // 创建新的首节点,并使之节点指向旧的首节点
  168. PNODE pNewOne = create_node(data);
  169. pNewOne->data = data;
  170. pNewOne->PNEXT = pNew->PNEXT;
  171.  
  172. // 头结点指向首节点
  173. pNew->PNEXT = pNewOne;
  174. return true;
  175. }
  176.  
  177. // 插入链表 N 位置
  178. bool list_N_insert(PNODE pHead,int n, int data)
  179. {
  180.  
  181. PNODE pNew = pHead;
  182. int i = ;
  183.  
  184. // 空的链表就不要插入了
  185. if (is_list_empty(pHead))
  186. {
  187. printf("空链表\n");
  188. return false;
  189. }
  190.  
  191. // 插入首节点
  192. if (n >len_list(pHead) + )
  193. {
  194. cout << "添加的位置大于链表的长度" << endl;
  195. return false;
  196. }
  197. else if ( == n )
  198. {
  199. return list_insert_head(pHead,data);
  200. }
  201. else if (n == len_list(pHead))
  202. {
  203. // 插入尾节点
  204. return list_insert_tail(pHead,data);
  205. }
  206. else
  207. {
  208. while ( (pNew->PNEXT != nullptr) && (i<n-))
  209. {
  210. // 在 N 的位置插入,则必须使得 pNew 指向 n-1 的位置
  211. pNew = pNew->PNEXT;
  212. i++;
  213. }
  214.  
  215. PNODE pNewOne = create_node(data);
  216. pNewOne->data = data;
  217. pNewOne->PNEXT = pNew->PNEXT;
  218. pNew->PNEXT = pNewOne;
  219.  
  220. return true;
  221. }
  222.  
  223. }
  224.  
  225. // 删除链表的首节点
  226. bool delete_list_heap(PNODE pHead)
  227. {
  228. if (is_list_empty(pHead))
  229. { // 空的链表的话,就没有什么好删除的
  230. printf("空链表,不需要删除\n");
  231. return false;
  232. }
  233.  
  234. // 指向首节点
  235. PNODE pNew = pHead->PNEXT;
  236. // 头结点指向第二个节点
  237. pHead->PNEXT = pNew->PNEXT;
  238. cout << "删除节点的数值是:" << pNew->data << endl;
  239.  
  240. free (pNew);
  241. pNew = nullptr;
  242.  
  243. return true;
  244. }
  245.  
  246. // 删除链表的尾节点
  247. bool delete_list_tail(PNODE pHead)
  248. {
  249. int i = NULL;
  250.  
  251. if (is_list_empty(pHead))
  252. {
  253. printf("空链表\n");
  254. return false;
  255. }
  256.  
  257. PNODE pNew = pHead;
  258. while ((pNew->PNEXT != nullptr) && (i < len_list(pHead))-)
  259. { // 使得 pNew 指向尾节点的倒数一个节点
  260. pNew = pNew->PNEXT;
  261. i++;
  262. }
  263.  
  264. PNODE pDelOne = pNew->PNEXT;
  265. cout << "删除节点的数值是:" << pDelOne->data << endl;
  266.  
  267. pNew->PNEXT = nullptr;
  268. free(pDelOne);
  269. pDelOne = nullptr;
  270.  
  271. return true;
  272. }
  273.  
  274. // 删除链表 N 位置
  275. bool delete_N_list(PNODE pHead, int n)
  276. {
  277. PNODE pNew = pHead;
  278. int i = ;
  279.  
  280. // 空的链表就不要插入了
  281. if (is_list_empty(pHead))
  282. {
  283. printf("空链表\n");
  284. return false;
  285. }
  286.  
  287. if (n > len_list(pHead))
  288. {
  289. cout << "删除的位置大于链表的长度" << endl;
  290. return false;
  291. }
  292. else if ( == n )
  293. {// 删除首节点
  294. return delete_list_heap(pHead);
  295. }
  296. else if (n == len_list(pHead))
  297. { // 删除尾节点
  298. return delete_list_tail(pHead);
  299. }
  300. else
  301. { // 删除除了首节点尾节点以外的节点,pNew 指向删除节点前面的那个节点
  302. while ((pNew->PNEXT != nullptr) && (i<n-) )
  303. {
  304. pNew = pNew->PNEXT;
  305. i++;
  306. }
  307.  
  308. PNODE pDelOne = pNew->PNEXT;
  309. pNew->PNEXT = pDelOne->PNEXT;
  310. cout << "删除节点的数值是:" << pDelOne->data << endl;
  311. free(pDelOne);
  312. pDelOne = nullptr;
  313.  
  314. return true;
  315. }
  316. }
  317.  
  318. // 链表的排序
  319. bool sort_list(PNODE pHead)
  320. {
  321. if (is_list_empty(pHead))
  322. {
  323. printf("空链表\n");
  324. return false;
  325. }
  326.  
  327. int n = len_list(pHead);
  328.  
  329. PNODE pp = nullptr;
  330. PNODE qq = nullptr;
  331. int i, j;
  332. int Temp;
  333.  
  334. for (pp = pHead->PNEXT, i = ; i < n-; i++, pp = pp->PNEXT)
  335. {
  336. for (qq = pp->PNEXT, j = i+; j < n; j++,qq = qq->PNEXT)
  337. {
  338. if ( pp->data > qq->data )
  339. {
  340. Temp = pp->data;
  341. pp->data = qq->data;
  342. qq->data = Temp;
  343. }
  344. }
  345. }
  346.  
  347. return true;
  348. }
  349.  
  350. int main(int argc, char *argv[])
  351. {
  352.  
  353. PNODE pHead = NULL;
  354. int iLen_lis = NULL;
  355.  
  356. // 创建链表已经初始化
  357. pHead = create_list();
  358.  
  359. // 链表的遍历
  360. traver_all_list(pHead);
  361.  
  362. // 计算链表长度
  363. iLen_lis = len_list(pHead);
  364. cout << "链表的长度是:" << iLen_lis << endl;
  365.  
  366. // 链表尾部添加数据
  367. if (list_append_tail(pHead, ))
  368. {
  369. cout << "链表的尾部添加数据成功" << endl;
  370.  
  371. iLen_lis = len_list(pHead);
  372. cout << "链表的长度是:" << iLen_lis << endl;
  373. traver_all_list(pHead);
  374. }
  375.  
  376. // 链表头部添加数据
  377. cout << endl;
  378. if (list_insert_head(pHead, ))
  379. {
  380. cout << "链表的首节点添加数据成功" << endl;
  381.  
  382. iLen_lis = len_list(pHead);
  383. cout << "链表的长度是:" << iLen_lis << endl;
  384. traver_all_list(pHead);
  385. }
  386.  
  387. // 指定位置插入数据
  388. cout << endl;
  389. if (list_N_insert(pHead,,))
  390. {
  391. cout << "插入成功" << endl;
  392. iLen_lis = len_list(pHead);
  393. cout << "链表的长度是:" << iLen_lis << endl;
  394. traver_all_list(pHead);
  395. }
  396.  
  397. // 指定位置删除数据
  398. cout << endl;
  399. if (delete_N_list(pHead, ))
  400. {
  401. cout << "删除成功" << endl;
  402. iLen_lis = len_list(pHead);
  403. cout << "链表的长度是:" << iLen_lis << endl;
  404. traver_all_list(pHead);
  405. }
  406.  
  407. // 链表的排序
  408. cout << endl;
  409. if ( sort_list(pHead) )
  410. {
  411. cout << "排序成功" << endl;
  412. traver_all_list(pHead);
  413. }
  414.  
  415. while ();
  416. }

经过自己的实测是正确的:

  1. 输出链表的长度,n =
  2.  
  3. 输出链表第 个的值1
  4. 输出链表第 个的值2
  5. 输出链表第 个的值3
  6.  
  7. 链表的长度是:
  8. 链表的尾部添加数据成功
  9. 链表的长度是:
  10.  
  11. 链表的首节点添加数据成功
  12. 链表的长度是:
  13.  
  14. 插入成功
  15. 链表的长度是:
  16.  
  17. 删除节点的数值是:
  18. 删除成功
  19. 链表的长度是:
  20.  
  21. 排序成功

补充:单链表的逆序

  1. bool reverse_list(PNODE pHead)
  2. {
  3. if (is_list_empty(pHead))
  4. {
  5. printf("空链表\n");
  6. return false;
  7. }
  8.  
  9. PNODE Temp0 = pHead;
  10. PNODE Temp1 = pHead;
  11. PNODE Temp3 = pHead->PNEXT;
  12. PNODE Temp2 = nullptr;
  13. int i = ;
  14. while (Temp3->PNEXT != nullptr)
  15. {
  16. Temp2 = Temp3;
  17. Temp3 = Temp3->PNEXT;
  18. if ( == i)
  19. {
  20. Temp2->PNEXT = nullptr;
  21. }
  22. else
  23. {
  24. Temp2->PNEXT = Temp1;
  25. }
  26. i++;
  27. Temp1 = Temp2;
  28. }
  29. Temp3->PNEXT = Temp2;
  30. Temp0->PNEXT = Temp3;
  31. return true;
  32. }

传入了头结点的指针,

首先 T2 接替 T3,T2指向了下一个节点,而 T1 接替 T2,就这样一部一部,使之 T2 永远指向 T1,当 T3 结束的时候,T3 是没有指向 T2 的,所以退出循环就执行Temp3->PNEXT = Temp2;,而头结点 T0->PNEXT

= T3.

双链表:

因为单链表的操作的不便(一旦指针指向一个节点,就无法返回来,必须重新进行循环),所以就引入了双向链表。

双向链表的数据定义:

  1. typedef struct Node
  2. {
  3. int data;
  4. struct Node * PPREV;
  5. struct Node * PNEXT;
  6. }NODE, *PNODE;

因为是双向链表,所以就定义了两个指向节点的指针,prev 往前指,next 指向后面的节点。

特殊的是:头结点的 prev 是指向尾节点(最后一个节点),而尾节点的 next 是指向头结点的。

代码:

  1. #define DEBUG
  2. #ifdef DEBUG
  3. #define DBG(fmt, args,...) printf(fmt, ##args)
  4. #else
  5. #define DBG(fmt, args...) do {} while (0)
  6. #endif
  7. typedef struct Node
  8. {
  9. int data;
  10. struct Node * PPREV;
  11. struct Node * PNEXT;
  12. }NODE, *PNODE;
  13. // 创建单个节点
  14. PNODE create_node(int data)
  15. {
  16. PNODE pNew = nullptr;
  17. pNew = (PNODE)malloc(sizeof(NODE));
  18. if ( nullptr == pNew)
  19. {
  20. cout << " malloc error" << endl;
  21. exit(-);
  22. }
  23. pNew->PNEXT = nullptr;
  24. pNew->PPREV = nullptr;
  25. pNew->data = data;
  26. return pNew;
  27. }
  28. // 链表的初始化
  29. PNODE create_list()
  30. {
  31. int i = ;
  32. int iLenList = NULL;
  33. int iDataList = NULL;
  34. PNODE pHead = nullptr;
  35. PNODE pTail = nullptr;
  36. pHead = (PNODE)malloc(sizeof(NODE));
  37. if ( nullptr == pHead )
  38. {
  39. cout << " malloc error" << endl;
  40. exit(-);
  41. }
  42. pHead->data = NULL;
  43. pHead->PNEXT = pHead->PPREV = nullptr;
  44. pTail = pHead;
  45.  
  46. printf("输入链表的长度 n = ");
  47. scanf("%d", &iLenList);
  48. for ( i = ; i < iLenList; i++)
  49. {
  50. printf("输入创建 第 %d 节点的数据\n", i + );
  51. scanf("%d", &iDataList);
  52. PNODE pNew = create_node( iDataList );
  53. pTail->PNEXT = pNew;
  54. pHead->PPREV = pNew;
  55. pNew->PPREV = pTail;
  56. pNew->PNEXT = pHead;
  57. pTail = pNew;
  58. }
  59. return pHead;
  60. }
  61. bool list_is_empty(PNODE pHead)
  62. {
  63. PNODE pNew = pHead;
  64. if ( pNew->PNEXT == nullptr )
  65. {
  66. return true;
  67. }
  68. else
  69. {
  70. return false;
  71. }
  72. }
  73. // 双向链表的遍历
  74. bool traver_list(PNODE pHead)
  75. {
  76. if ( list_is_empty(pHead))
  77. {
  78. cout << "空链表" << endl;
  79. return false;
  80. }
  81. PNODE pNew = pHead, pHeadNew = pHead;
  82. while (pNew->PNEXT != pHeadNew)
  83. {
  84. pNew = pNew->PNEXT;
  85. cout << pNew->data << endl;
  86. }
  87. return true;
  88. }
  89. // 计算链表的长度
  90. int len_list(PNODE pHead)
  91. {
  92. PNODE pHeadNew = pHead;
  93. int i = NULL;
  94. PNODE pNew = pHead;
  95. while ( pNew->PNEXT != pHeadNew )
  96. {
  97. pNew = pNew->PNEXT;
  98. i++;
  99. }
  100. return i;
  101. }
  102. // 链表的尾部添加数据
  103. bool list_tail_append_data(PNODE pHead, int data)
  104. {
  105. PNODE pHeadNew = pHead;
  106. PNODE pTail = pHead;
  107. DBG("%d \n", __LINE__);
  108. int n = len_list(pHead);
  109. int i = ;
  110. // 抱枕 pTail 指向最后一个节点
  111. while ( i < (n) ) // 从头结点到尾节点需要移动 n(链表长度)次数
  112. {
  113. pTail = pTail->PNEXT;
  114. i++;
  115. }
  116. // 创建新的节点
  117. PNODE pNewOne = (PNODE)malloc(sizeof(NODE));
  118. if ( nullptr == pNewOne )
  119. {
  120. cout << "malloc error" << endl;
  121. return false;
  122. }
  123. pNewOne->data = data;
  124. DBG("%d \n", __LINE__);
  125. pTail->PNEXT = pNewOne;
  126. pNewOne->PPREV = pTail;
  127. pNewOne->PNEXT = pHeadNew;
  128. pHeadNew->PPREV = pNewOne;
  129. DBG("%d \n", __LINE__);
  130. return true;
  131. }
  132. // 链表的头部添加数据
  133. bool list_heap_insert_data(PNODE pHead, int data)
  134. {
  135. if ( list_is_empty(pHead))
  136. {
  137. printf("空链表 \n");
  138. return false;
  139. }
  140. PNODE pHeadNew = pHead;
  141. PNODE pNewOne = (PNODE)malloc(sizeof(NODE));
  142. pNewOne->data = data;
  143. pNewOne->PNEXT = pHeadNew->PNEXT;
  144. pHeadNew->PNEXT->PPREV = pNewOne;
  145. pHeadNew->PNEXT = pNewOne;
  146. return true;
  147. }
  148. bool list_tail_insert(PNODE pHead,int data)
  149. {
  150. PNODE pHeadNew = pHead;
  151. PNODE pNew = pHead;
  152. int i = ;
  153. int n = len_list(pHeadNew);
  154. while (i<(n-))
  155. { // pNew 指向尾节点的前面一个节点,
  156. i++;
  157. pNew = pNew->PNEXT;
  158. }
  159. PNODE pNewOne = (PNODE)malloc(sizeof(NODE));
  160. pNewOne->data = data;
  161. pNewOne->PNEXT = pNew->PNEXT;
  162. pNew->PNEXT->PPREV = pNewOne;
  163. pNewOne->PNEXT = pNew;
  164. pNew->PNEXT = pNewOne;
  165. return true;
  166. }
  167. // 指定位置插入数据
  168. bool list_N_insert(PNODE pHead, int iPos, int data)
  169. {
  170. PNODE pHeadNew = pHead;
  171. PNODE pNew = pHead;
  172. if (list_is_empty(pHead))
  173. {
  174. printf("空链表 \n");
  175. return false;
  176. }
  177. int i = NULL;
  178. int n = len_list(pHeadNew);
  179. if ( iPos > n)
  180. {
  181. printf("出入位置大于链表的长度,不能执行插入\n");
  182. return false;
  183. }
  184. else if ( n == )
  185. {// 在首结点插入数据
  186. return list_heap_insert_data(pHeadNew, data);
  187. }
  188. else if ( n == iPos)
  189. {// 在尾节点插入数据
  190. return list_tail_insert(pHeadNew,data);
  191. }
  192. else
  193. {
  194. // 在除了尾节点首节点插入数据
  195. while (i < (iPos - ))
  196. {// 使得 pNew 指向删除节点的前面一个节点
  197. pNew = pNew->PNEXT;
  198. i++;
  199. }
  200. PNODE pNewOne = create_node(data);
  201. pNewOne->PNEXT = pNew->PNEXT;
  202. pNew->PNEXT->PPREV = pNewOne;
  203. pNew->PNEXT = pNewOne;
  204. pNewOne->PPREV = pNew;
  205. return true;
  206. }
  207. }
  208. // 删除链表首节点
  209. bool delete_heap_list(PNODE pHead)
  210. {
  211. PNODE pHeadNew = pHead;
  212. PNODE pNew;
  213. int n = len_list(pHead);
  214. if ( n == )
  215. {
  216. pNew = pHeadNew->PNEXT;
  217. pHeadNew->PNEXT = pHeadNew;
  218. pHeadNew->PNEXT = pHeadNew;
  219. printf("删除数据是%d\n", pNew->data);
  220. free(pNew);
  221. pNew = nullptr;
  222. return true;
  223. }
  224. else
  225. {
  226. pNew = pHeadNew->PNEXT;
  227. pHeadNew->PNEXT = pHeadNew->PNEXT->PNEXT;
  228. pHeadNew->PNEXT->PPREV = pHeadNew;
  229. printf("删除数据是%d\n", pNew->data);
  230. free(pNew);
  231. pNew = nullptr;
  232. return true;
  233. }
  234. }
  235. // 删除链表的尾节点
  236. bool delete_tail_list(PNODE pHead)
  237. {
  238. PNODE pHeadNew = pHead;
  239. PNODE pNew = pHead;
  240. PNODE pNewOne = nullptr;
  241. int n = len_list(pHead);
  242. int i = NULL;
  243. while (i < (n - ))
  244. {// pNew 指向删除节点的前面一个节点
  245. pNew = pNew->PNEXT;
  246. i++;
  247. }
  248. pNewOne = pNew->PNEXT;
  249. printf("删除数据是%d\n", pNewOne->data);
  250. pNew->PNEXT = pHeadNew;
  251. pHeadNew->PPREV = pNew;
  252. free(pNewOne);
  253. pNewOne = nullptr;
  254.  
  255. return true;
  256. }
  257. // 删除链表的任意的位置
  258. bool delete_N_list(PNODE pHead,int iPos)
  259. {
  260. if (list_is_empty(pHead))
  261. {
  262. printf("空链表 \n");
  263. return false;
  264. }
  265. PNODE pNew = pHead;
  266. PNODE pNewOne = pHead;
  267. int i = NULL;
  268. int n = len_list(pHead);
  269. if (iPos > n)
  270. {
  271. printf("删除位置大于链表的长度,不能执行删除\n");
  272. return false;
  273. }
  274. else if ( iPos == )
  275. {
  276. return delete_heap_list(pHead);
  277. }
  278. else if (iPos == n)
  279. {
  280. return delete_tail_list(pHead);
  281. }
  282. else
  283. {
  284. while (i<(iPos - ))
  285. {
  286. pNew = pNew->PNEXT;
  287. i++;
  288. }
  289. pNewOne = pNew->PNEXT;
  290. pNew->PNEXT = pNewOne->PNEXT;
  291. pNewOne->PNEXT->PPREV = pNew;
  292. printf("删除数据是%d\n", pNewOne->data);
  293. free(pNewOne);
  294. pNewOne = nullptr;
  295. return true;
  296. }
  297. }
  298. // 对链表进行排序
  299. bool list_sort(PNODE pHead)
  300. {
  301. if (list_is_empty(pHead))
  302. {
  303. printf("空链表,排序失败\n");
  304. return false;
  305. }
  306. int n = len_list(pHead);
  307. PNODE ppNew = nullptr;
  308. PNODE qqNew = nullptr;
  309. int i = , j = ;
  310. int TempDat = NULL;
  311. for (ppNew = pHead->PNEXT, i = ; i < (n - );i++,ppNew = ppNew->PNEXT)
  312. {
  313. for (qqNew = ppNew->PNEXT, j = i+; j < n;j++,qqNew=qqNew->PNEXT)
  314. {
  315. if ( ppNew->data > qqNew->data)
  316. {
  317. TempDat = ppNew->data;
  318. ppNew->data = qqNew->data;
  319. qqNew->data = TempDat;
  320. }
  321. }
  322. }
  323. return true;
  324. }
  325. int main(int argc, char **argv)
  326. {
  327. int iLenList = NULL;
  328. PNODE pHead = nullptr;
  329. // 双向链表的初始化
  330. pHead = create_list();
  331. if (traver_list(pHead))
  332. {
  333. cout << "遍历成功" << endl;
  334. }
  335. iLenList = len_list(pHead);
  336. printf("链表长度等于 %d\n", iLenList);
  337. // 链表的尾部添加数据
  338. if (list_tail_append_data(pHead, ))
  339. {
  340. list_tail_append_data(pHead, );
  341. printf("尾部添加数据成功\n");
  342. traver_list(pHead);
  343. }
  344. if (list_heap_insert_data(pHead, ))
  345. {
  346. list_heap_insert_data(pHead, );
  347. printf("头部添加数据成功\n");
  348. traver_list(pHead);
  349. }
  350. // 链表的任意位置添加数据
  351. if (list_N_insert(pHead,,))
  352. {
  353. printf("位置3添加数据成功\n");
  354. traver_list(pHead);
  355. }
  356. // 任意位置删除数据
  357. if (delete_N_list(pHead, ))
  358. {
  359. printf("位置8删除数据成功\n");
  360. traver_list(pHead);
  361. }
  362. // 排序
  363. if ( list_sort(pHead) )
  364. {
  365. printf("排序成功\n");
  366. traver_list(pHead);
  367. }
  368. while ();
  369. }

双链表和单链表的操作其实很多的类似,参考者编写代码,还是比较简单的。参照了单链表,也是设置了头结点用于帮助设计双向链表,然后还有首尾节点,这些才是正真保存数据的开始的节点和结束的节点。

二、内核链表的学习

对于链表的操作自己编写的话过于麻烦,而Linux内核提供了对应的API,可以直接调用方便使用:D:\source insight\linux2.6.35.7\android-kernel-samsung-dev\include\linux 的 list.h。

0、链表节点的指针

  1. struct list_head
  2. {
  3. struct list_head *next, *prev;
  4. };

链表结构体的指针有两个:next 指向下一个节点,prev 指向上一个节点。也就是说内核链表具备了双向链表的功能,而且链表只是纯链表,并不具备数据类型,所以具备非常大的通用性,这样可以自己根据自己的实际的需求去设计。

1、链表头结点的初始化

(1)定义且初始化

  1. #define LIST_HEAD_INIT(name) { &(name), &(name) }
  2. #define LIST_HEAD(name) \
  3. struct list_head name = LIST_HEAD_INIT(name)

链表头结点定义的时候且完成初始化,将上面的宏进行进行展开:

  1. #define LIST_HEAD(name) \
  2. struct list_head name = { &(name), &(name) }

是将链表的头结点的两个指针分别都指向了自己,从而完成链表头结点的初始化。

(2)先定义后完成初始化

  1. static inline void INIT_LIST_HEAD(struct list_head *list)
  2. {
  3. list->next = list;
  4. list->prev = list;
  5. }

对于一个已经完成链表头节点初始化,那么对这个链表头则是调用这个函数来完成初始化。这里函数实现链表头节点的初始化与上面宏完成初始化是一样的,差别无非是定义且初始化,一个是先定义头结点后完成初始化。

2、链表节点的添加

  1. static inline void __list_add(struct list_head *new,
  2. struct list_head *prev,
  3. struct list_head *next)
  4. {
  5. next->prev = new;
  6. new->next = next;
  7. new->prev = prev;
  8. prev->next = new;
  9. }

对于链表节点的添加,这里需要知道,默认的都是进行尾添加,也就是在节点的后面进行添加。

注意:

学习发现,内核的双向链表是其实也是借助了头结点了。

2.1、链表的头结点进行添加

  1. static inline void list_add(struct list_head *new, struct list_head *head)
  2. {
  3. __list_add(new, head, head->next);
  4. }

new 指向全新的节点,而 head 是指向头节点,而head->next 是指头首节点的下一个节点,也就是第二个节点。所以插入的节点 new 是在第一个和第二个节点直接之间完成节点的插入。

2.2、链表尾部节点的添加

  1. static inline void list_add_tail(struct list_head *new, struct list_head *head)
  2. {
  3. __list_add(new, head->prev, head);
  4. }

new 指向新的节点,而 head->prev 指向链表的尾节点,head 指向链表的首节点。所以 new 是被插入在首节点和尾节点之间。

3、链表删除一个节点

  1. static inline void __list_del(struct list_head * prev, struct list_head * next)
  2. {
  3. next->prev = prev;
  4. prev->next = next;
  5. }

  1. static inline void list_del(struct list_head *entry)
  2. {
  3. __list_del(entry->prev, entry->next);
  4. entry->next = LIST_POISON1;
  5. entry->prev = LIST_POISON2;
  6. }

entry 是指向删除节点的指针。 entry->prev 是指向删除节点的前面一个节点,而 entrt->next 指向删除节点的下一个节点。而将删除节点的 prev 和 next 分别设置为 position,对它的定义为:

  1. /*
  2. * These are non-NULL pointers that will result in page faults
  3. * under normal circumstances, used to verify that nobody uses
  4. * non-initialized list entries.
  5. */
  6. #define LIST_POISON1 ((void *) 0x00100100 + POISON_POINTER_DELTA)
  7. #define LIST_POISON2 ((void *) 0x00200200 + POISON_POINTER_DELTA)

理解内核的注释, position 不是一个空指针,但是会引起页的错误。

4、节点的替换

  1. static inline void list_replace(struct list_head *old,
  2. struct list_head *new)
  3. {
  4. new->next = old->next;
  5. new->next->prev = new;
  6. new->prev = old->prev;
  7. new->prev->next = new;
  8. }

代码还是比较的简单,完成新老节点替换,使得新的节点的指针指向老节点的指向。

  1. static inline void list_replace_init(struct list_head *old,
  2. struct list_head *new)
  3. {
  4. list_replace(old, new);
  5. INIT_LIST_HEAD(old);
  6. }

完成新老节点的替换,然后将老节点进行初始化。

5、节点的移动

  1. /**
  2. * list_move - delete from one list and add as another's head
  3. * @list: the entry to move
  4. * @head: the head that will precede our entry
  5. */
  6. static inline void list_move(struct list_head *list, struct list_head *head)
  7. {
  8. __list_del(list->prev, list->next);
  9. list_add(list, head);
  10. }

将 list 指向的节点从 head 为开始的头结点的链表删除之后,又移动到这个链表的首节点。也就是在头结点的后面。

  1. /**
  2. * list_move_tail - delete from one list and add as another's tail
  3. * @list: the entry to move
  4. * @head: the head that will follow our entry
  5. */
  6. static inline void list_move_tail(struct list_head *list,
  7. struct list_head *head)
  8. {
  9. __list_del(list->prev, list->next);
  10. list_add_tail(list, head);
  11. }

将节点 list 从链表 中删除,并将节点 list 添加到链表(以 head 为头结点)的尾部。

6、链表的判断

  1. /**
  2. * list_is_last - tests whether @list is the last entry in list @head
  3. * @list: the entry to test
  4. * @head: the head of the list
  5. */
  6. static inline int list_is_last(const struct list_head *list,
  7. const struct list_head *head)
  8. {
  9. return list->next == head;
  10. }

判断节点 list 是不是链表的最后一个节点。

list 是判断的链表的节点。

head:是双向量表的头结点。

代码和简单,就是判断 list 节点的下一个节点是不是 head(头结点)。

  1. /**
  2. * list_empty - tests whether a list is empty
  3. * @head: the list to test.
  4. */
  5. static inline int list_empty(const struct list_head *head)
  6. {
  7. return head->next == head;
  8. }

判断链表是否为空:

其实就是判断自己的下一个节点是不是指向了自己,因为只有空的链表,也就是只有一个头结点的话,才会自己指向自己。

  1. /**
  2. * list_empty_careful - tests whether a list is empty and not being modified
  3. * @head: the list to test
  4. *
  5. * Description:
  6. * tests whether a list is empty _and_ checks that no other CPU might be
  7. * in the process of modifying either member (next or prev)
  8. *
  9. * NOTE: using list_empty_careful() without synchronization
  10. * can only be safe if the only activity that can happen
  11. * to the list entry is list_del_init(). Eg. it cannot be used
  12. * if another CPU could re-list_add() it.
  13. */
  14. static inline int list_empty_careful(const struct list_head *head)
  15. {
  16. struct list_head *next = head->next;
  17. return (next == head) && (next == head->prev);
  18. }

判断链表是否为空,这次的判断是通过头结点的前驱和后驱是不是指向同一个节点。按照字面上面的解释,是怕在判断的时候被CPU 的其他的线程修改了数据,因此这种判断的方法是比较的正确的。

7、链表的首节点放到尾节

  1. /**
  2. * list_rotate_left - rotate the list to the left
  3. * @head: the head of the list
  4. */
  5. static inline void list_rotate_left(struct list_head *head)
  6. {
  7. struct list_head *first;
  8. if (!list_empty(head)) {
  9. first = head->next;
  10. list_move_tail(first, head);
  11. }
  12. }

显示判断链表不为空的时候,让链表的首节点放到尾节点。

8、判断链表是否只有一个的节点(首节点)

  1. /**
  2. * list_is_singular - tests whether a list has just one entry.
  3. * @head: the list to test.
  4. */
  5. static inline int list_is_singular(const struct list_head *head)
  6. {
  7. return !list_empty(head) && (head->next == head->prev);
  8. }

当链表只有一个节点的时候,也就是存在一个头结点和首节点,所以这个时候头结点的头指针和尾指针都是指向首节点的。

9、链表的遍历

  1. #define list_entry(ptr, type, member) \
  2. container_of(ptr, type, member)

type :结构体的类型

member:结构体的成员变量

ptr:返回结构体的起始的地址,我估计这个点应该是链表的头结点的地址


  1. #define list_first_entry(ptr, type, member) \
  2. list_entry((ptr)->next, type, member)

返回结构体的初始地址的下一个节点的初始地址(我估计应该是链表的首节点的地址)。

上面的介绍,已经可以基本对内核有了基本的认识。

三、内核链表的使用

内核链表提供的都是纯链表,而对于链表的数据类型是通过自己灵活指定的,是将链表的结构整个内嵌到链表的结构体里面。

  1. struct
  2. {
  3. int goal;
  4. int id;
  5. struct list_head head;
  6. }

goal和 id 是数据,而 head 在是链表的指针的结构;而自己可以灵活去设置自己的数据区域。

因为单链表的操作的不便(一旦指针指向一个节点,就无法返回来,必须重新进行循环),所以就引入了双向链表。

对于链表的操作自己编写的话过于麻烦,而Linux内核提供了对应的API,可以直接调用方便使用:D:\source insight\linux2.6.35.7\android-kernel-samsung-dev\include\linux 的 list.h。

0、链表节点的指针

struct list_head

{

struct list_head *next, *prev;

};

链表结构体的指针有两个:next 指向下一个节点,prev 指向上一个节点。

1、链表头结点的初始化

(1)定义且初始化

#define LIST_HEAD_INIT(name) { &(name), &(name) }

 

#define LIST_HEAD(name) \

struct list_head name = LIST_HEAD_INIT(name)

链表头结点定义的时候且完成初始化,将上面的宏进行进行展开:

#define LIST_HEAD(name) \

struct list_head name = { &(name), &(name) }

是将链表的头结点的两个指针分别都指向了自己,从而完成链表头结点的初始化。

(2)先定义后完成初始化

static inline void INIT_LIST_HEAD(struct list_head *list)

{

list->next = list;

list->prev = list;

}

对于一个已经完成链表头节点初始化,那么对这个链表头则是调用这个函数来完成初始化。这里函数实现链表头节点的初始化与上面宏完成初始化是一样的,差别无非是定义且初始化,一个是先定义头结点后完成初始化。

Linux内核中链表的学习的更多相关文章

  1. linux内核中链表代码分析---list.h头文件分析(一)【转】

    转自:http://blog.chinaunix.net/uid-30254565-id-5637596.html linux内核中链表代码分析---list.h头文件分析(一) 16年2月27日17 ...

  2. linux内核中链表代码分析---list.h头文件分析(二)【转】

    转自:http://blog.chinaunix.net/uid-30254565-id-5637598.html linux内核中链表代码分析---list.h头文件分析(二) 16年2月28日16 ...

  3. Linux内核中链表实现

    关于双链表实现,一般教科书上定义一个双向链表节点的方法如下: struct list_node{ stuct list_node *pre; stuct list_node *next; ElemTy ...

  4. Linux内核中链表的实现与应用【转】

    转自:http://blog.chinaunix.net/uid-27037833-id-3237153.html 链表(循环双向链表)是Linux内核中最简单.最常用的一种数据结构.         ...

  5. Linux内核中的机制学习总结

    一.驱动中的poll机制 1.简介:select()和poll()系统调用的本质一样,前者在 BSD UNIX 中引入的,后者在 System V 中引入的. 应用程序使用 select() 或 po ...

  6. Linux内核中的list用法和实现分析

    这些天在思考知识体系的完整性,发现总是对消息队列的实现不满意,索性看看内核里面的链表实现形式,这篇文章就当做是学习的i笔记吧.. 内核代码中有很多的地方使用了list,而这个list的用法又跟我们平时 ...

  7. linux内核中的C语言常规算法(前提:你的编译器要支持typeof和type)

    学过C语言的伙伴都知道,曾经比较两个数,输出最大或最小的一个,或者是比较三个数,输出最大或者最小的那个,又或是两个数交换,又或是绝对值等等,其实这些算法在linux内核中通通都有实现,以下的代码是我从 ...

  8. Linux内核中的算法和数据结构

    算法和数据结构纷繁复杂,但是对于Linux Kernel开发人员来说重点了解Linux内核中使用到的算法和数据结构很有必要. 在一个国外问答平台stackexchange.com的Theoretica ...

  9. LINUX内核分析第二周学习总结——操作系统是如何工作的

    LINUX内核分析第二周学习总结——操作系统是如何工作的 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course ...

随机推荐

  1. css iframe边框去掉

    [IE6以下] iframe边框通过css设定在FF下正常在ie下却还存在边框,通过在iframe标签内部设置属性 frameborder="no" border="0& ...

  2. 【07】react 之 生命周期

    阅读目录(Content) 实例化 getDefaultProps getInitialState componentWillMount render componentDidMount 存在期 co ...

  3. httpclient与webapi

    System.Net.Http 是微软推出的最新的 HTTP 应用程序的编程接口, 微软称之为“现代化的 HTTP 编程接口”, 主要提供如下内容: 1. 用户通过 HTTP 使用现代化的 Web S ...

  4. linux解决无法打开资源管理器

    前两天升级系统,使用命令pacman -Syyu,大概是使用的是testing缘故,今天发现dolphin无法打开了,使用命令行打开,提示ldmp.so有问题. 解决方法如下: 一,使用命令:pacm ...

  5. 一个.java文件定义多个类的情况

    一个.java文件中定义多个类: 注意一下几点: (1) public权限类只能有一个(也可以一个都没有,但最多只有一个): (2)这个.java文件名只能是public 权限的类的类名: (3)倘若 ...

  6. Android进阶之Fragment与Activity之间的数据交互

    1 为什么 因为Fragment和Activity一样是具有生命周期,不是一般的bean通过构造函数传值,会造成异常. 2 Activity把值传递给Fragment 2.1 第一种方式,也是最常用的 ...

  7. TensorFlow——Checkpoint为模型添加检查点

    1.检查点 保存模型并不限于在训练模型后,在训练模型之中也需要保存,因为TensorFlow训练模型时难免会出现中断的情况,我们自然希望能够将训练得到的参数保存下来,否则下次又要重新训练. 这种在训练 ...

  8. 可靠UDP设计

    最近加入了一个用帧同步的项目,帧同步方案对网络有着极大的影响,于是采用了RUDP(可靠UDP),那么为什么要摒弃TCP,而费尽心思去采用UDP呢?要搞明白这个问题,首先要了解TCP和UDP的区别 , ...

  9. CodeForces - 103D Time to Raid Cowavans

    Discription As you know, the most intelligent beings on the Earth are, of course, cows. This conclus ...

  10. 分布式配置中心介绍--Spring Cloud学习第六天(非原创)

    文章大纲 一.分布式配置中心是什么二.配置基本实现三.Spring Cloud Config服务端配置细节(一)四.Spring Cloud Config服务端配置细节(二)五.Spring Clou ...