【數據結構】順序表和鏈表
一、順序表
順序表定義 :順序表是在計算機內存中以數組的形式保存的線性表,線性表的順序存儲是指用一組地址連續的存儲單元依次存儲線性表中的各個元素、使得線性表中在邏輯結構上相鄰的數據元素存儲在相鄰的物理存儲單元中,即通過數據元素物理存儲的相鄰關系來反映數據元素之間邏輯上的相鄰關系,采用順序存儲結構的線性表通常稱為順序表。順序表是將表中的結點依次存放在計算機內存中一組地址連續的存儲單元中。
順序表可以分為靜態順序表和動態順序表,靜態較為簡單,本文提供全部動態順序表基本操作的代碼。
順序表的基本操作:
1、順序表的構建 (采用結構體構建順序表)代碼如下:
1 #define MAXSIZE 100
2
3 typedef int Datatype;
4
5 typedef struct SequenceTable //靜態順序表
6 {
7 Datatype _data[MAXSIZE];
8 Datatype _size;
9 }Seq;
10
11 typedef struct SeqList //動態順序表
12 {
13 Datatype *_data;
14 Datatype _size;
15 size_t _capacity;
16 }SeqList;
2、順序表的初始化
1 void SeqListInit(SeqList *list)
2 {
3 assert(list);
4 list->_capacity = 0;
5 list->_size = 0;
6 list->_data = NULL;
7 }
3、順序表的增,刪,查,改,打印,求元素個數,銷毀順序表
1 void PrintSeqList(SeqList *list) //打印順序表
2 {
3 int i = 0;
4 assert(list);
5 for(i=0; i<list->_size; i++)
6 printf("%d ",list->_data[i]);
7 printf("\n");
8 }
9
10 void PushSeqlist(SeqList* list,Datatype pos,Datatype x) //插入元素
11 {
12 int i = list->_size;
13 assert(list);
14 if (pos>list->_size)
15 return;
16 CheckSeqCapacity(list);
17 while (i != pos)
18 {
19 list->_data[i] = list->_data[i-1];
20 i--;
21 }
22 list->_data[i] = x;
23 list->_size++;
24 }
25
26 void CheckSeqCapacity(SeqList *list)
27 {
28 assert(list);
29 if (list->_capacity == list->_size)
30 {
31 Datatype *tmp = (Datatype*)realloc(list->_data,(list->_capacity * 2+3)*sizeof(Datatype));
32 assert(tmp);
33 list->_data = tmp;
34 list->_capacity = list->_capacity*2+3;
35 }
36 }
37
38 void DeleteSeqlist(SeqList *list,Datatype x) //刪除元素
39 {
40 int pos = SearchSeqlist(list,x);
41 assert(list);
42 if (pos>=0)
43 {
44 int i = pos;
45 while (i < list->_size-1)
46 {
47 list->_data[i] = list->_data[i+1];
48 i++;
49 }
50 list->_size--;
51 }
52 }
53
54 size_t SeqlistSize(SeqList *list)//求順序表元素個數
55 {
56 assert(list);
57 return list->_size;
58 }
59
60 Datatype SearchSeqlist(SeqList *list,Datatype x) //在順序表中查找元素
61 {
62 int i = 0;
63 assert(list);
64 for(i=0; i<list->_size; i++)
65 {
66 if (list->_data[i] == x)
67 return i;
68 }
69 return -1;
70 }
71
72 void ChangeSeqlist(SeqList *list,Datatype x,Datatype dst) //修改元素
73 {
74 int pos = SearchSeqlist(list,x);
75 assert(list);
76 if (pos>=0)
77 list->_data[pos] = dst;
78 }
79
80 void ClearSeqlist(SeqList *list) //清空順序表
81 {
82 assert(list);
83 list->_size = 0;
84 list->_capacity = 0;
85 free(list->_data);
86 list->_data = NULL;
87 }
順序表的基本操作較為簡單,來說說順序表的優缺點:
原理:順序表存儲是將數據元素放到一塊連續的內存存儲空間,存取效率高,速度快。但是不可以動態增加長度
優點:存取速度高效,通過下標來直接存儲
缺點:1.插入和刪除比較慢,2.不可以增長長度
比如:插入或者刪除一個元素時,整個表需要遍歷移動元素來重新排一次順序
鏈表:(單鏈表,雙鏈表(無頭單雙鏈表,帶頭節點單雙鏈表,循環單雙鏈表))
鏈表是一種物理存儲單元上非連續、非順序的存儲結構,數據元素的邏輯順序是通過鏈表中的指針鏈接次序實現的。鏈表由一系列結點(鏈表中每一個元素稱為結點)組成,結點可以在運行時動態生成。每個結點包括兩個部分:一個是存儲數據元素的數據域,另一個是存儲下一個結點地址的指針域。
鏈表的基本操作:(本博文寫出了單鏈表的基本操作)
鏈表的結構的構建
typedef int Datatype;
typedef struct LinkList //單鏈表結構
{
Datatype _data;//數據域
struct LinkList *_next; //指針域
}LinkList;
typedef struct doublelist //雙鏈表
{
Datatype _data;
struct doublelist *_prev; //前趨
struct doublelist *_next; //後繼
}doublelist;
節點的創建
1 LinkList* BuyListNode(Datatype x) //創建節點
2 {
3 LinkList *newNode = (LinkList *)malloc(sizeof(LinkList));
4 assert(newNode);
5 newNode->_next = NULL;
6 newNode->_data = x;
7 return newNode;
8 }
單鏈表的增,刪,查,改,打印,排序,逆置,銷毀。
1 void PrintLinkList(LinkList *phead) //鏈表打印
2 {
3 assert(phead);
4 while (phead)
5 {
6 printf("%d ",phead->_data);
7 phead = phead->_next;
8 }
9 printf("\n");
10 }
11
12 void LinkListInsert(LinkList **pphead,LinkList *pos,Datatype x) //鏈表的插入(包括頭插,隨機插入)
13 {
14 LinkList *newNode = BuyListNode(x);
15 assert(pphead);
16 if (*pphead == NULL)
17 {
18 newNode->_next = *pphead;
19 *pphead = newNode;
20 }
21 else
22 {
23 pos->_data ^= newNode->_data;
24 newNode->_data ^= pos->_data;
25 pos->_data ^= newNode->_data;
26 newNode->_next = pos->_next;
27 pos->_next = newNode;
28 }
29 }
30
31 void LinkListDelete(LinkList **pphead,Datatype x)//鏈表元素的刪除
32 {
33 LinkList *tmp =LinkListSearch(*pphead,x);
34 LinkList *phead = *pphead;
35 assert(pphead);
36 if (tmp == *pphead) //頭刪
37 *pphead = tmp->_next;
38 else if (tmp)
39 {
40 if (tmp->_next) //中間刪除
41 {
42 LinkList *cur = tmp->_next;
43 tmp->_data = cur->_data;
44 tmp->_next = cur->_next;
45 free(cur);
46 cur = NULL;
47 }
48 else //尾刪
49 {
50 LinkList * cur = NULL;
51 while (phead->_next)
52 {
53 cur = phead;
54 phead = phead->_next;
55 }
56 cur->_next = NULL;
57 free(tmp);
58 tmp = NULL;
59 }
60 }
61 }
62
63 LinkList *LinkListSearch(LinkList *phead,Datatype x) //鏈表元素查找
64 {
65 assert(phead);
66 while (phead)
67 {
68 if (phead->_data == x)
69 {
70 return phead;
71 }
72 phead = phead->_next;
73 }
74 return NULL;
75 }
76
77 void LinkListChange(LinkList *phead,Datatype x,Datatype y) //鏈表元素修改
78 {
79 LinkList *tmp = LinkListSearch(phead,x);
80 assert(phead);
81 if (tmp)
82 tmp->_data = y;
83 }
84
85 void LinkListSort(LinkList *phead)//鏈表排序(選擇法排序)
86 {
87 int len = 0;
88 int i = 0;
89 LinkList *tmp = phead,*flag = NULL;
90 assert(phead);
91 while(tmp) //元素個數
92 {
93 len++;
94 tmp = tmp->_next;
95 }
96 while(--len)
97 {
98 int num = 0;
99 LinkList *cur = NULL;
100 tmp = phead;
101 flag = tmp;
102 for (i = 0; i<len; i++)
103 {
104 cur = tmp->_next;
105 if (flag->_data > cur->_data)
106 flag = cur; //記錄最大數
107 tmp = tmp->_next;
108 }
109 num = flag->_data; //將最大數放在最後
110 flag->_data = tmp->_data;
111 tmp->_data = num;
112 }
113 }
114
115 LinkList *LinkListReverse(LinkList *pphead) //單鏈表的逆置
116 {
117 assert(pphead);
118 if (pphead->_next) //有1個以上節點
119 {
120 LinkList *cur = pphead;
121 LinkList *next = cur->_next;
122 cur->_next = NULL;
123 while (next) //逆置過程
124 {
125 LinkList *tmp = next->_next;
126 next->_next = cur;
127 cur = next;
128 next = tmp;
129 }
130 return cur; //返回新的頭節點
131 }
132 else
133 return pphead;
134 }
135
136 void DestoryLinkList(LinkList *list) //鏈表的銷毀
137 {
138 LinkList *tmp = list;
139 while (list)
140 {
141 list = tmp;
142 tmp = tmp->_next;
143 free(list);
144 list = NULL;
145 }
146 }
鏈表的優缺點:
原理:鏈表存儲是在程序運行過程中動態的分配空間,只要存儲器還有空間,就不會發生存儲溢出問題
優點:插入和刪除速度快,保留原有的物理順序,比如:插入或者刪除一個元素時,只需要改變指針指向即可
缺點:查找速度慢,因為查找時,需要循環鏈表訪問
從它們的存儲優缺點來看,各自有各自的使用場景,比如:頻繁的查找卻很少的插入和刪除操作可以用順序表存儲,如果頻繁的插入和刪除操作很少的查詢就可以使用鏈表存儲
鏈表相關面試題:
鏈表的從尾到頭打印
1 void PrintLinkListrR(LinkList *phead) //遞歸打印
2 {
3 if (phead == NULL)
4 return;
5 PrintLinkListrR(phead->_next);
6 printf("%d ",phead->_data);
7 }
8
9 void PrintLinkListTail(LinkList *phead) //利用尾指針前移依次打印
10 {
11 LinkList *tail = NULL;
12 LinkList *tmp = phead;
13 assert(phead);
14 while (tail != phead)
15 {
16 tmp = phead;
17 while (tmp->_next != tail)
18 tmp = tmp->_next;
19 printf("%d ",tmp->_data);
20 tail = tmp;
21 }
22 }
刪除一個無頭單鏈表的非尾節點(不能遍歷鏈表)
1 void LinkListNoHead(LinkList *pos)
2 {
3 LinkList *tmp = pos->_next; //保存pos下一個位置
4 assert(pos);
5 pos->_data = tmp->_data;
6 pos->_next = tmp->_next;
7 free(tmp);
8 tmp = NULL;
9 }
在無頭單鏈表的一個節點前插入一個節點(不能遍歷鏈表)
void LinkListNoHeadInsert(LinkList *pos,Datatype x)
{
LinkList *newNode = BuyListNode(x);
assert(pos);
newNode->_next = pos->_next; //插入節點
pos->_next = newNode;
//交換數據
pos->_data ^= newNode->_data;
newNode->_data ^= pos->_data;
pos->_data ^= newNode->_data;
}
單鏈表實現約瑟夫環(JosephCircle)
void LinkListJosephCircle(LinkList *phead,Datatype x)
{
LinkList *tail = phead;
int count = 0;
assert(phead);
while (tail->_next)
tail = tail->_next;
tail->_next = phead; //構成環
while (phead != phead->_next)
{
if (count == (x-1)) //到了指定元素就刪掉該元素
{
LinkList *tmp = phead->_next;
phead->_next = tmp->_next;
count = 0;
free(tmp);
tmp = NULL;
}
phead = phead->_next;
count++;
}
printf("%d \n",phead->_data); //最後剩下的一個元素
}
單鏈表排序(冒泡排序)
1 void Swap(int* p1,int* p2)
2 {
3 int tmp = *p1;
4 *p1 = *p2;
5 *p2 = tmp;
6 }
7 void LinkListBubbleSort(LinkList *phead)
8 {
9 int flag = 1;
10 LinkList *tail =NULL;
11 LinkList *tmp = phead;
12 assert(phead);
13 while (tmp->_next != tail) //冒泡總次數
14 {
15 LinkList *cur = phead;
16 while (cur->_next != tail) //每次冒泡多少個數
17 {
18 if (cur->_data > cur->_next->_data)
19 {
20 flag = 0;
21 Swap(&cur->_data,&cur->_next->_data);
22 }
23 cur = cur->_next;
24 }
25 tail = cur; //該指針前移
26 if (flag) //優化冒泡次數
27 return;
28 }
29 }
合並兩個有序鏈表,合並後依然有序
1 LinkList *CombineDoublelinklist(LinkList *list1,LinkList *list2)
2 {
3 LinkList *list = NULL;
4 LinkList *tail = NULL;
5 assert(list1 && list2);
6 if(list1->_data < list2->_data) //找出小的一個做表頭
7 {
8 list = list1;
9 list1 = list1->_next;
10 }
11 else
12 {
13 list = list2;
14 list2 = list2->_next;
15 }
16 tail = list;
17 while (list1->_next && list2->_next)
18 {
19 if(list1->_data < list2->_data) //把小的一個鏈在tail後面
20 {
21 tail->_next = list1;
22 list1 = list1->_next;
23 }
24 else
25 {
26 tail->_next = list2;
27 list2 = list2->_next;
28 }
29 tail = tail->_next;
30 }
31 if (list1->_next) //把不為空的鏈表鏈接起來
32 tail->_next = list1;
33 else
34 tail->_next = list2;
35 return list;
36 }
查找單鏈表的中間節點,要求只能遍歷一次鏈表
1 LinkList *SearchMidNode(LinkList *phead)
2 {
3 LinkList *fast = phead;
4 LinkList *slow = phead;
5 assert(phead);
6 while (fast && fast->_next ) //快指針的速度是慢指針的二倍。
7 {
8 fast = fast->_next->_next;
9 slow = slow->_next;
10 }
11 return slow;
12 }
查找單鏈表的倒數第k個節點,要求只能遍歷一次鏈表
1 LinkList *SearchKNode(LinkList *phead,Datatype k)
2 {
3 LinkList *fast = phead;
4 LinkList *slow = phead;
5 assert(phead);
6 while (k--) //先走k步
7 fast = fast->_next;
8 while (fast) //在同時走
9 {
10 fast = fast->_next;
11 slow = slow->_next;
12 }
13 return slow;
14 }
刪除鏈表的倒數第K個結點
1 void DeleteKNode(LinkList *phead,Datatype k)
2 {
3 LinkList *fast = phead;
4 LinkList *slow = phead;
5 LinkList *tmp = slow;
6 assert(phead);
7 while (k--) //先走k步
8 fast = fast->_next;
9 while (fast) //在同時走
10 {
11 tmp = slow;
12 fast = fast->_next;
13 slow = slow->_next;
14 }
15 tmp->_next = slow->_next;
16 free(slow);
17 slow = NULL;
18 }
判斷單鏈表是否帶環?若帶環,求環的長度?求環的入口點?
1 void CreateCircle(LinkList *list,LinkList *entry) //創建一個環用於測試
2 {
3 assert(list);
4 while (list->_next)
5 {
6 list = list->_next;
7 }
8 list->_next = entry; //構成環
9 }
10 LinkList *CheckIsCircle(LinkList *phead) //帶環返回環中相遇點,防止返回null
11 {
12 LinkList *fast = phead;
13 LinkList *slow = phead;
14 assert(phead);
15 do
16 {
17 fast = fast->_next->_next;
18 slow = slow->_next;
19 }while(fast != slow && fast);
20 if (fast)
21 return slow;
22 return NULL;
23 }
24
25 int CircleSize(LinkList *phead,LinkList *meet)
26 {
27 int size = 0;
28 LinkList *tmp = meet;
29 assert(phead);
30 do
31 {
32 tmp = tmp->_next;
33 size++;
34 }while (meet != tmp);
35 return size;
36 }
37
38 LinkList *CircleEntry(LinkList *phead,LinkList* meet)
39 {
40 LinkList *start = phead;
41 assert(phead);
42 while (start != meet)
43 {
44 start = start->_next;
45 meet = meet->_next;
46 }
47 return meet;
48 }
判斷兩個鏈表是否相交,若相交,求交點。(假設鏈表不帶環)
1 void CreateTogetherNode(LinkList *list1,LinkList *list2,LinkList *node1,LinkList *node2) //創造相交鏈表用於測試
2 {
3 assert(list1 && list2);
4 node2->_next = node1;
5 }
6 static LinkList *LinkListtTogetherNode(LinkList *list1,LinkList *list2) //求交點地址
7 {
8 int m1 = 0;
9 int m2 = 0;
10 LinkList *tmp1 = list1;
11 LinkList *tmp2 = list2;
12 while (tmp1 || tmp2) //求出兩個鏈表總長度
13 {
14 if (tmp1)
15 {
16 m1++;
17 tmp1 = tmp1->_next;
18 }
19 if (list2)
20 {
21 m2++;
22 tmp2 = tmp2->_next;
23 }
24 }
25 tmp1 = list1;
26 tmp2 = list2;
27 if (m1>m2) //長的先走
28 {
29 int len = m1 - m2;
30 while (len--)
31 tmp1 = tmp1->_next;
32 }
33 else
34 {
35 int len = m2 - m1;
36 while (len--)
37 tmp2 = tmp2->_next;
38 }
39 while (tmp1 != tmp2) //一樣長了同時走,走道交點處停下。
40 {
41 tmp1 = tmp1->_next;
42 tmp2 = tmp2->_next;
43 }
44 return tmp1; //返回交點地址
45 }
46
47 LinkList *LinkListSameNode(LinkList*list1,LinkList *list2) //判斷是否相交,相交返回交點,否則返回空
48 {
49 LinkList *m1 = list1;
50 LinkList *m2 = list2;
51 assert(m1 && list2);
52 while (m1->_next)
53 m1 = m1->_next;
54 while (m2->_next)
55 m2 = m2->_next;
56 if(m1 == m2) //相交
57 {
58 return LinkListtTogetherNode(list1,list2);
59 }
60 return NULL;
61 }
判斷兩個鏈表是否相交,若相交,求交點。(假設鏈表可能帶環)【升級版】
分情況(1、不相交 2、相交不帶環 3、相交帶環(a、環內相交 b、環外相交) )
1 static LinkList *LinkListCircleNode(LinkList *list1,LinkList *meet1,LinkList *list2,LinkList *meet2) //求帶環相交的交點
2 {
3 LinkList *m1 = CircleEntry(list1,meet1); //求得入口地址
4 LinkList *m2 = CircleEntry(list2,meet2);
5 assert(list1 && list2 && meet1 && meet2);
6 if (m1 != m2)//環內相交
7 return m1;
8 else //環外相交
9 {
10 m1 = NULL;
11 m2 = NULL;
12 return LinkListtTogetherNode(list1,list2);
13 }
14 }
15 LinkList *LinkListSameNodeCircle(LinkList *list1,LinkList *list2)
16 {
17 LinkList *node = NULL;
18 LinkList *meet1 = CheckIsCircle(list1);
19 LinkList *meet2 = CheckIsCircle(list2);
20 assert(list1 && list2);
21 if (meet1 && meet2) //都帶環
22 {
23 LinkList *tmp1 = meet1->_next;
24 LinkList *tmp2 = meet2;
25 while (tmp1 != meet1)
26 {
27 if (tmp1 == tmp2) //帶環相交
28 {
29 node = LinkListCircleNode(list1,meet1,meet2,list2);
30 return node;
31 }
32 tmp1 = tmp1->_next;
33 }
34 return NULL; //帶環不相交
35
36 }
37 else if (meet1 == NULL && meet2 == NULL) //兩鏈表都不帶環
38 {
39 node = LinkListSameNode(list1,list2);
40 if(node) //不帶環相交
41 return node;
42 return NULL; //不帶環不相交
43 }
44 else //其他不相交的情況
45 return NULL;
46 }
求兩個已排序單鏈表中相同的數據。
1 void TwoListSamedata(LinkList *list1,LinkList *list2)
2 {
3 assert(list1 && list2);
4 LinkListBubbleSort(list1); //排序兩鏈表
5 LinkListBubbleSort(list2);
6 while (list1 && list2)
7 {
8 if (list1->_data < list2->_data)
9 list1 = list1->_next;
10 else if(list2->_data< list1->_data)
11 list2 = list2->_next;
12 else
13 {
14 printf("%d ",list2->_data); //相等就打印出來,當然這裏也可以把相等的數據保存起來,單獨打印
15 list1 = list1->_next;
16 list2 = list2->_next;
17 }
18 }
19 printf("\n");
20 }
總結:
順序表存儲位置是相鄰連續的,可以隨即訪問的一種數據結構,一個順序表在使
用前必須指定起長度,一旦分配內存,則在使用中不可以動態的更改。他的優點是訪問數據是比較方便,可以隨即的訪問表中的任何一個數據,缺點是定義的長度不可更改造成存儲空間的浪費。
鏈表是通過指針來描述元素關系的一種數據結構,他可以是物理地址不連續的物理空間。不能隨即訪問鏈表元素,必須從表頭開始,一步一步搜索元素。它的優點是:對於數組,可以動態的改變數據的長度,分配物理空間。
建議:
在使用中如果一個數組在使用中,查詢比較多,而插入,刪除數據比較少,數組的長度不變時,選順序表比較合理。如果插入,刪除,長度不定的數組,可以選鏈表。
如果有哪些地方不清楚的可以私信我。
喜歡這篇文建就順手推薦,方便大家看到
【數據結構】順序表和鏈表