双向 链表

简介: 双向 链表

一、双向链表的实现

带头、双向、循环  

头部的prev指向尾部,

List.h

1. #pragma once
2. #include <stdio.h>
3. #include <stdlib.h>
4. #include <assert.h>
5. 
6. typedef int LTDataType;
7. 
8. typedef struct ListNode
9. {
10.   LTDataType data;
11.   struct ListNode* next;
12.   struct ListNode* prev;
13. }LTNode;
14. 
15. void ListPrint(LTNode* phead);
16. //void ListInit(LTNode** pplist);//初始化,二级指针
17. LTNode* ListInit(LTNode* phead);
18. void ListPushBack(LTNode* phead, LTDataType x);//尾插
19. void ListPopBack(LTNode* phead);//尾删
20. void ListPushFront(LTNode* phead, LTDataType x);
21. void ListPopFront(LTNode* phead);
22. LTNode* ListFind(LTNode* phead, LTDataType x);
23. //pos之前插入
24. void ListInsert(LTNode* pos, LTDataType x);
25. //删除pos位置的节点
26. void ListErase(LTNode* pos);
27. void ListDestory(LTNode* phead);
28. 
29. 
30.

List.c

1. #define _CRT_SECURE_NO_WARNINGS 1
2. #include "List.h"
3. 
4. 
5. 
6. 
7. LTNode* BuyLTNode(LTDataType x)
8. {
9.  LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
10.   if (newnode == NULL)
11.   {
12.     printf("malloc fail\n");
13.     exit(-1);
14.   }
15.   newnode->prev = NULL;
16.   newnode->data = x;
17.   newnode->next = NULL;
18. }
19. 
20. 初始化,二级指针的思路;
21. //void ListInit(LTNode** pphead)//初始化,next指向自己,prev指向自己,才算是循环,头的地址还不清楚//初始化个哨兵位
22. //{
23. //  assert(pphead);
24. //  *pphead = BuyLTNode(0);
25. //  (*pphead)->prev = *pphead;
26. //  (*pphead)->next = *pphead;
27. //}
28. 
29. //初始化,一级指针的思路,返回头即可,既可以改变头的地址,又可以传一级指针
30. LTNode* ListInit(LTNode* phead)
31. {
32.   phead = BuyLTNode(0);
33.   phead->prev = phead;
34.   phead->next = phead;
35.   return phead;
36. }
37. //打印
38. void ListPrint(LTNode* phead)
39. {
40.   //从head的next = cur->data开始打印,当cur->next = head结束
41.   assert(phead);
42.   LTNode* cur = phead->next;
43.   while (cur != phead)
44.   {
45.     printf("%d ", cur->data);
46.     cur = cur->next;
47.   }
48.   printf("\n");
49. }
50. 
51. 
52. void ListPushBack(LTNode* phead, LTDataType x)
53. {
54.   assert(phead);//带头
55.   ListInsert(phead, x);
56.   /*LTNode* tail = phead->prev;
57.   LTNode* newnode = BuyLTNode(x);
58.   tail->next = newnode;
59.   newnode->prev = tail;
60.   newnode->next = phead;
61.   phead->prev = newnode;*/
62. }
63. //尾删,尾删到没有节点,也不用处理
64. void ListPopBack(LTNode* phead)
65. {
66.   assert(phead);
67.   //链表为空
68.   assert(phead->next != phead);
69.   /*LTNode* tail = phead->prev;
70.   LTNode* tailPrev = tail->prev;
71.   phead->prev = tailPrev;
72.   tailPrev->next = phead;
73.   free(tail);
74.   tail = NULL;*/
75.   ListErase(phead->prev);
76. }
77. 
78. //头插
79. void ListPushFront(LTNode* phead, LTDataType x)
80. {
81.   assert(phead);
82.   ListInsert(phead->next, x);
83.   /*LTNode* newnode = BuyLTNode(x);
84.   LTNode* next = phead->next;
85.   next->prev = newnode;
86.   newnode->next = next;
87.   phead->next = newnode;
88.   newnode->prev = phead;*/
89. }
90. //头删
91. void ListPopFront(LTNode* phead)
92. {
93.   assert(phead);
94.   ListErase(phead->next);
95.   /*assert(phead->next != phead);
96.   LTNode* head = phead->next;
97.   LTNode* next = head->next;
98.   phead->next = next;
99.   next->prev = phead;
100.  free(head);
101.  head = NULL;*/
102. }
103. //查找
104. LTNode* ListFind(LTNode* phead, LTDataType x)
105. {
106.  assert(phead);
107.  LTNode* cur = phead->next;
108.  while (cur != phead)
109.  {
110.    if (cur->data == x)
111.    {
112.      return cur;
113.    }
114.    cur = cur->next;
115.  }
116.  return NULL;
117. }
118. //插入
119. void ListInsert(LTNode* pos, LTDataType x)
120. {
121.  assert(pos);
122.  LTNode* newnode = BuyLTNode(x);
123.  LTNode* prev = pos->prev;
124.  prev->next = newnode;
125.  newnode->prev = prev;
126.  newnode->next = pos;
127.  pos->prev = newnode;
128. }
129. 
130. //删除
131. void ListErase(LTNode* pos)//在主函数中,pos的实参要置空,否则实参就会成为野指针
132. {
133.  assert(pos);
134.  LTNode* next = pos->next;
135.  LTNode* prev = pos->prev;
136.  prev->next = next;
137.  next->prev = prev;
138.  free(pos);
139.  pos = NULL;
140. }
141. //传一级指针是为了保持接口一致性
142. void ListDestory(LTNode* phead)//注意,phead的实参要进行free,否则会导致野指针
143. {
144.  assert(phead);
145.  LTNode* cur = phead->next;
146.  while (cur != phead)
147.  {
148.    LTNode* next = phead->next;
149.    free(cur);
150.    cur = next;
151.  }
152.  free(phead);
153.  phead = NULL;
154. }

(1)首先进行哨兵位的初始化,哨兵位进行初始化之后,地址就不会再发生变化,所以在进行头插、尾插时,不需要传二级指针,仅仅需要哨兵位即可【尾插、头插也是在哨兵位之后】

(2)改变的是结构体的地址,改变的是指针的内容,改变指针的内容,需要传递指针的地址。

二、顺序表和带头双向循环链表的区别

这两个结构是相辅相成的结构,

顺序表:优点:(1)物理空间是连续的,方便用下标随机访问。【二分查找、排序】

(2)CPU高速缓存命中率会更高。

缺点:(1)由于需要物理空间连续,空间不够需要扩容。扩容本身有一定的消耗,其次扩容机制还存在一定的空间消耗。(2)头部或者中间的插入删除,需要挪动数据,效率低。O(N)

链表:优点(1)按需申请释放空间(2)任意位置可以O(1)的插入删除数据

缺点:不支持下标的随机访问,有些算法不适合在他上面进行。如:二分查找、排序等

编译连接之后,生成可执行程序,CPU执行这个程序,CPU要去访问内存,CPU不会直接访问内存,CPU嫌弃内存速度太慢,会把数据加载到三级缓存或者寄存器。4或者8byte小数据放到寄存器,大数据放到缓存。CPU会看数据是否在缓存,在的话就命中,直接访问,不在的话,先把数据从内存加载到缓存,再访问。会缓存一段连续的空间,所以顺序表命中更高。

相关文章
|
7月前
|
存储
【数据结构】双向带头循环链表的实现
【数据结构】双向带头循环链表的实现
|
7月前
|
存储 缓存
[数据结构]~双向+循环链表从(0~1)
[数据结构]~双向+循环链表从(0~1)
|
7月前
|
缓存 算法
带头+双向+循环链表
带头+双向+循环链表
|
7月前
|
存储
【数据结构】【版本1.2】【线性时代】——链表之王(双向带头循环)
【数据结构】【版本1.2】【线性时代】——链表之王(双向带头循环)
|
7月前
|
存储
数据结构 模拟实现LinkedList双向不循环链表
数据结构 模拟实现LinkedList双向不循环链表
75 1
|
7月前
顺序表与链表(双向)优劣势
顺序表与链表(双向)优劣势
55 0
|
存储 C++
【双向链表】带头双向循环(1)
【双向链表】带头双向循环(1)
65 0
09 单向循环链表
09 单向循环链表
26 0
【数据结构】双向带头循环链表
【数据结构】双向带头循环链表