目录
前言:上次我们实现了单链表,而这个链表与单链表殊途同归。
建议先学过单链表后再学习带头双向链表,学过单链表之后会发现这个很简单。
单链表链接:https://blog.youkuaiyun.com/qq_60750110/article/details/123808059?spm=1001.2014.3001.5501
结构上相较单链表要复杂一点,但是实现上要比单链表简单很多。
我实现的这个链表内部的插入和删除等,里面一部分的代码顺序都是可以改变的,不需要完全一样。
一.带头双向链表逐步实现
1.创建结构体
我创建了3个文件分别为DList.h, DList.c, Test.c
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;//存链表下一个节点地址
struct ListNode* prev;//存链表上一个节点地址
}ListNode;
typedef的作用在上一篇单链表已经详细说明过了,这里就不再说了。
2.初始化链表
ListNode* BuyListNode(LTDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
ListNode* InitList()
{
ListNode* phead = BuyListNode(0);
phead->next = phead;
phead->prev = phead;
return phead;
}
初始化链表时,为了方便之后的插入,可以创建一个函数BuyListNode来创建新节点。
因为是带哨兵位的头,所以这个头节点不实际存数据,只是起到方便的作用,而且在后面打印链表时,也不会去打印该头节点。
又因为是双向循环链表,所以我们让phead->next = phead, phead->prev = phead。这样就可以初步实现一个双向循环链表了。
3.打印链表
void ListPrint(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
创建一个临时变量cur,然后根据循环链表的特性,当cur = cur->next,经过循环之后,最后当cur = phead时,就说明已经遍历了一遍链表,所以条件为cur != phead.
4.尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* newnode = BuyListNode(x);
ListNode* tail = phead->prev;
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
尾插就要插在原本存在的最后一个节点的后面,当只有一个头节点时该代码同样可以,不存在特殊条件。
因为是双向循环链表,所以phead->prev就是该链表的最后一个节点,这时就分别让最后一个节点的next存新节点的地址,让新节点的prev存原来最后一个节点的地址,next存头节点的地址,最后把头节点的prev的地址改为这个新节点的地址。
5.尾删
void ListPopBack(ListNode* phead)
{
assert(phead);
assert(phead->next != phead);
ListNode* tail = phead->prev;
ListNode* tailPrev = tail->prev;
free(tail);
tail = NULL;
tailPrev->next = phead;
phead->prev = tailPrev;
}
尾删这里有个断言是assert(phead->next != phead),因为如果这个链表只有一个头节点时,是没有必要删除的,所以进行该断言。
尾删需要知道尾节点的地址和尾节点的前一个节点的地址,所以我们可以设置两个变量tail和tailPrev分别存这两个地址。然后free掉尾节点之后,再让tailPrev存上头节点的地址,并且改头节点的prev为tailPrev的地址。
6.头插
void ListPushFront(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* newnode = BuyListNode(x);
ListNode* next = phead->next;
phead->next = newnode;
newnode->prev = phead;
newnode->next = next;
next->prev = newnode;
}
头插要插在头节点的后面,因为这个头节点是哨兵位节点,不属于这个链表中的数据。
所以我们可以用next存头节点的下一个节点的地址,然后再使phead的next存上新的头节点的地址,然后让新的头节点的prev和next分别存上phead和原phead的下一个节点的地址,最后再让原头节点的下一个节点的prev存上头节点的新的下一个节点的地址。
7.头删
void ListPopFront(ListNode* phead)
{
assert(phead);
assert(phead->next != phead);
ListNode* next = phead->next;
ListNode* nextNext = next->next;
phead->next = nextNext;
nextNext->prev = phead;
free(next);
next = NULL;
}
头删需要知道头节点的下一个节点的地址,以及头节点的下一个节点的下一个节点的地址,这里我们用next和nextNext来保存。
然后分别让头节点的next从原本头节点的下一个节点的地址改为nextNext的地址,然后再让改nextNext的prev的地址存上phead的地址,最后free掉next即可。
8.查找
ListNode* ListFind(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
查找与打印类似,通过定义一个临时变量存上phead的next的地址,然后遍历链表,寻找与所给值x相等的数据节点,然后返回该节点。
9.插入
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* newnode = BuyListNode(x);
ListNode* posPrev = pos->prev;
newnode->next = pos;
pos->prev = newnode;
posPrev->next = newnode;
newnode->prev = posPrev;
}
实现这个插入后,我们可以用该插入去代替尾插和头插,并且时间复杂度都一样,都为O(1)。
我们首先保存pos节点的上一个节点的地址,然后让新节点的next保存pos的地址,再让pos的prev改为新节点的地址,之后让原本pos节点的上一个节点的next保存新节点的地址,最后再让新节点的prev保存原本pos节点的上一个节点的地址
10.删除
实现这个删除后,我们可以用该删除去代替尾删和头删,并且时间复杂度都一样,都为O(1)。
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* next = pos->next;
free(pos);
pos = NULL;
prev->next = next;
next->prev = prev;
}
分别用prev和next保存该pos的上一个节点的地址和下一个节点的地址。
然后free掉pos之后,分别让pos的上一个节点的next保存pos的下一个节点的地址,让pos的下一个节点的prev保存pos的上一个节点的地址。
11.销毁链表
void ListDestory(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
遍历链表进行free即可。
二.代码
1.DList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;//存链表下一个节点地址
struct ListNode* prev;//存链表上一个节点地址
}ListNode;
void ListPrint(ListNode* pList);//打印链表
ListNode* InitList();//初始化链表
void ListPushBack(ListNode* phead, LTDataType x);//尾插
void ListPopBack(ListNode* phead);//尾删
void ListPushFront(ListNode* phead, LTDataType x);//头插
void ListPopFront(ListNode* phead);//头删
ListNode* ListFind(ListNode* phead, LTDataType x);//查找
//在pos之前插入
void ListInsert(ListNode* pos, LTDataType x);//插入
void ListErase(ListNode* pos);//删除
void ListDestory(ListNode* phead);//销毁链表
2.DList.c
#include "DList.h"
ListNode* BuyListNode(LTDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
void ListPrint(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
ListNode* InitList()
{
ListNode* phead = BuyListNode(0);
phead->next = phead;
phead->prev = phead;
return phead;
}
void ListPushBack(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* newnode = BuyListNode(x);
ListNode* tail = phead->prev;
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
void ListPopBack(ListNode* phead)
{
assert(phead);
assert(phead->next != phead);
ListNode* tail = phead->prev;
ListNode* tailPrev = tail->prev;
free(tail);
tail = NULL;
tailPrev->next = phead;
phead->prev = tailPrev;
}
void ListPushFront(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* newnode = BuyListNode(x);
ListNode* next = phead->next;
phead->next = newnode;
newnode->prev = phead;
newnode->next = next;
next->prev = newnode;
}
void ListPopFront(ListNode* phead)
{
assert(phead);
assert(phead->next != phead);
ListNode* next = phead->next;
ListNode* nextNext = next->next;
phead->next = nextNext;
nextNext->prev = phead;
free(next);
next = NULL;
}
ListNode* ListFind(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* newnode = BuyListNode(x);
ListNode* posPrev = pos->prev;
newnode->next = pos;
pos->prev = newnode;
posPrev->next = newnode;
newnode->prev = posPrev;
}
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* next = pos->next;
free(pos);
pos = NULL;
prev->next = next;
next->prev = prev;
}
void ListDestory(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
3.Test.c
#include "DList.h"
void TestList1()
{
ListNode* pList = InitList();
ListPushBack(pList, 1);
ListPushBack(pList, 2);
ListPushBack(pList, 3);
ListPushBack(pList, 4);
ListPrint(pList);
ListPopBack(pList);
ListPopBack(pList);
ListPrint(pList);
}
void TestList2()
{
ListNode* pList = InitList();
ListPushBack(pList, 1);
ListPushBack(pList, 2);
ListPushBack(pList, 3);
ListPushBack(pList, 4);
ListPrint(pList);
ListPushFront(pList, 0);
ListPushFront(pList, -1);
ListPushFront(pList, -2);
ListPrint(pList);
ListPopFront(pList);
ListPopFront(pList);
ListPopFront(pList);
ListPopFront(pList);
ListPopFront(pList);
ListPopFront(pList);
ListPrint(pList);
}
void TestList3()
{
ListNode* pList = InitList();
ListPushBack(pList, 1);
ListPushBack(pList, 2);
ListPushBack(pList, 3);
ListPushBack(pList, 4);
ListPrint(pList);
ListNode* pos = ListFind(pList, 3);
if (pos)
{
ListInsert(pos, 10);
}
ListPrint(pList);
if (pos)
{
ListErase(pos);
}
ListPrint(pList);
ListDestory(pList);
}
int main()
{
TestList1();
TestList2();
TestList3();
return 0;
}
三.用Insert和Erase代替尾插头插尾删头删
1.尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
assert(phead);
ListInsert(phead, x);
}
2.头插
void ListPushFront(ListNode* phead, LTDataType x)
{
assert(phead);
ListInsert(phead->next, x);
}
3.尾删
void ListPopBack(ListNode* phead)
{
assert(phead);
assert(phead->next != phead);
ListErase(phead->prev);
}
4.头删
void ListPopFront(ListNode* phead)
{
assert(phead);
assert(phead->next != phead);
ListErase(phead->next);
}