原來長這樣
我們在第一篇《算法與數(shù)據(jù)結(jié)構(gòu)》里用到的鏈表就是雙鏈表。但是在本篇博客,換一個角度,以功能操作(創(chuàng)建、插入、刪除、更新、遍歷等)為切入點,橫向比較 雙向鏈表 雙向循環(huán)鏈表
鏈表結(jié)構(gòu)對比
節(jié)點相同
兩種鏈表的節(jié)點
首尾節(jié)點前后指向不同,注意:下圖中黑線表示指向節(jié)點,不是節(jié)點的data部分
image
創(chuàng)建方式對比
只有頭節(jié)點
區(qū)別:循環(huán)鏈表里,頭指針前后均指向自己
創(chuàng)建雙向鏈表
/// 創(chuàng)建鏈表
Status CreateList(LinkList *L)
{
*L = (LinkList)malloc(sizeof(Node));
if ((*L) == NULL) return ERROR;
(*L)->prior = NULL;
(*L)->next = NULL;
return OK;
}
創(chuàng)建雙向循環(huán)鏈表
/// 創(chuàng)建鏈表
Status CreateList(LinkList *L)
{
*L = (LinkList)malloc(sizeof(Node));
if ((*L) == NULL) return ERROR;
(*L)->prior = *L; // 這里和上面不一樣,指向自己,
(*L)->next = *L; // 這里和上面不一樣,指向自己
/*
* 和雙鏈表的稍微差異處理之后,只有頭節(jié)點自己也可以是一個循環(huán)
*/
return OK;
}
插入節(jié)點對比
在任意位置插入節(jié)點
(上圖為兩種鏈表的插入任意位置)
雙鏈表的插入
p:插入位置前的節(jié)點 q:插入位置后的節(jié)點 s:新節(jié)點
- 創(chuàng)建新節(jié)點s
- 找到插入位置前的一個節(jié)點p
- 判斷是不是插入在尾節(jié)點之后
- 是,新節(jié)點s的prior指向尾節(jié)點p,尾節(jié)點p的next指向新節(jié)點s
- 不是插入在尾節(jié)點之后,
- p的next節(jié)點q的prior 指向新節(jié)點s
- 新節(jié)點s的next指向q
- 新節(jié)點s的prior指向p
- p的next指向s
- over
Status ListInsert(LinkList *L, int place , ElemType v)
{
// 有頭節(jié)點 所以插入位置不能是<1
if (place < 1) return ERROR;
// 新節(jié)點
LinkList temp = (LinkList)malloc(sizeof(Node));
if (temp == NULL) return ERROR;
temp->data = v;
temp->prior = NULL;
temp->next = NULL;
// 找到插入前的節(jié)點
LinkList p = *L;
// 找到插入位置 (place 位置的前一個結(jié)點)
for (int i = 1; i < place; i++) {
if (p->next == NULL) break;
p = p->next;
}
// 注意:因為有頭節(jié)點,所有新節(jié)點一定是在頭節(jié)點之后,所以頭指針L,一定指向頭節(jié)點
if (p->next == NULL)
{
p->next = temp;
temp->prior = p;
}
else
{
// 1、p的后一個節(jié)點的prior指向新節(jié)點temp
p->next->prior = temp;
// 2、新節(jié)點temp的next指向p的后一個節(jié)點
temp->next = p->next;
// 3、新節(jié)點temp的prior指向p
temp->prior = p;
// 4、p的next指向新節(jié)點temp
p->next = temp;
}
return 1;
}
雙向循環(huán)鏈表的插入
p:插入位置前的節(jié)點 q:插入位置后的節(jié)點 s:新節(jié)點
- 創(chuàng)建新節(jié)點s
- 找到插入位置前的一個節(jié)點p
- 判斷是不是插入在尾節(jié)點之后
- p的next節(jié)點q的prior 指向新節(jié)點s
- 新節(jié)點s的next指向q
- 新節(jié)點s的prior指向p
- p的next指向s
- over
為什么比雙鏈表少判斷是不是尾節(jié)點?
因為有頭節(jié)點的存在,并且是個循環(huán),即使插入在尾節(jié)點,也和任意位置插入一樣
Status ListInsert(LinkList *L, int place , ElemType v)
{
// 有頭節(jié)點 所以插入位置不能是<1
if (place < 1) return ERROR;
// 新節(jié)點
LinkList temp = (LinkList)malloc(sizeof(Node));
if (temp == NULL) return ERROR;
temp->data = v;
temp->prior = NULL;
temp->next = NULL;
// 找到插入前的節(jié)點
// 這里為什么和下面刪除、替換的p=*L->next不同,而是p=*L:我們要找到place位置的前一個節(jié)點,所以要默認(rèn)從頭節(jié)點開始
LinkList p = *L;
// 找到插入位置 (place 位置的前一個結(jié)點)
for (int i = 1; i < place; i++) {
if (p->next == (*L))
break; // 說明此時P已經(jīng)是尾節(jié)點
p = p->next;
}
temp->next = p->next;
temp->next->prior = temp;
p->next = temp;
temp->prior = p;
return OK;
}
區(qū)別:雙向鏈表多了尾節(jié)點后插入操作
刪除節(jié)點對比
刪除任意位置的節(jié)點
(上圖為兩種鏈表刪除任意位置)
雙鏈表的刪除
老話重提,注意是不是刪除尾節(jié)點,因為尾節(jié)點的next需要為null
Status ListDelete(LinkList *L, int place, ElemType *v)
{
// 有頭節(jié)點 所以插入位置不能是<1
if (place < 1) return ERROR;
// 找到place位置的節(jié)點
LinkList p = (*L)->next; // 從首元節(jié)點開始
for (int i = 1; i < place; i++) {
if (p->next == NULL) break;
p = p->next;
}
*v = p->data;
if (p->next == NULL)
{
// p是尾節(jié)點
// 將p的前一個節(jié)點變成尾節(jié)點
p->prior->next = NULL;
// 釋放p
free(p);
}
else
{
// p是任意位置的節(jié)點
// 1、將p的后一個節(jié)點指向p的前一個節(jié)點
p->next->prior = p->prior;
// 2、將p的前一個節(jié)點指向p的后一個節(jié)點
p->prior->next = p->next;
// 3、釋放
free(p);
}
return 1;
}
雙向循環(huán)鏈表的刪除
循環(huán)鏈表不需要考慮是不是尾節(jié)點,因為他有下一個節(jié)點,把下一個節(jié)點和他前一個節(jié)點建立互相指向,釋放自己。為什么在本篇博客中沒有提及頭指針的變更? 因為我們有頭節(jié)點。
Status ListDelete(LinkList *L, int place, ElemType *v)
{
// 有頭節(jié)點 所以插入位置不能是<1
if (place < 1) return ERROR;
// 找到place位置的節(jié)點
LinkList p = (*L)->next; // 從首元節(jié)點開始
for (int i = 1; i < place; i++) {
if (p->next == NULL) break;
p = p->next;
}
*v = p->data;
if (p->next == NULL)
{
// p是尾節(jié)點
// 將p的前一個節(jié)點變成尾節(jié)點
p->prior->next = NULL;
// 釋放p
free(p);
}
else
{
// p是任意位置的節(jié)點
// 1、將p的后一個節(jié)點指向p的前一個節(jié)點
p->next->prior = p->prior;
// 2、將p的前一個節(jié)點指向p的后一個節(jié)點
p->prior->next = p->next;
// 3、釋放
free(p);
}
return 1;
}
區(qū)別:雙鏈表多了尾節(jié)點刪除處理
更新節(jié)點對比
- 找到要操作的節(jié)點
- 更新data
兩種鏈表步驟一樣
圖片可以參考上面刪除的圖片,在找到后,僅更改節(jié)點data即可
Status ListReplace(LinkList *L, int place, ElemType newValue)
{
// 有頭節(jié)點 所以插入位置不能是<1
if (place < 1) return ERROR;
// 找到place位置的節(jié)點
LinkList p = (*L)->next; // 從首元節(jié)點開始
for (int i = 1; i < place; i++) {
if (p->next == NULL) break;
p = p->next;
}
p->data = newValue;
return 1;
}
查找指定位置的節(jié)點
按照上文替換、刪除的方式找到要操作的place位置的節(jié)點。
兩種鏈表在不涉及更改操作,單純的執(zhí)行查找時,是一樣的
/// 查找節(jié)點
/// @param L 鏈表(沒有使用指針,所以L是拷貝的鏈表,不是原鏈表!?。。?/// @param place 查找的位置
/// @param node 要查找的節(jié)點(C語言不好的同學(xué),這里*node實際是存儲節(jié)點地址的指針,與 function(LinkList *L) 一樣,用了雙指針)
Status FindNode(LinkList L, int place, LinkList *node)
{
if (L == NULL) {
printf("打印的雙向循環(huán)鏈表為空!\n\n");
return ERROR;
}
// 有頭節(jié)點 所以插入位置不能是<1
if (place < 1) return ERROR;
// 找到place位置的節(jié)點
LinkList p = L->next; // 從首元節(jié)點開始
int i = 1;
for (; i < place; i++) {
if (p->next == NULL) break;
p = p->next;
}
if (i < place) {
printf("位置超出鏈表長度!");
return ERROR;
}
*node = p;
return OK;
}
主函數(shù)
int main(int argc, const char * argv[]) {
LinkList L;
int place, value;
CreateList(&L);
DisplayList(L);
/*
*
* 。。。。。。。。
* ListInsert()插入測試數(shù)據(jù)
*/
printf("查詢節(jié)點,請輸入查詢位置:\n");
LinkList node = NULL;
scanf("%d", &place);
FindNode(L, place, &node);
if (node != NULL) {
printf("節(jié)點:%p, data:%d\n",node,node->data);
}
}
查找輸出結(jié)果