我们在单链表中,有了next指针这就使得我们查找下一个元素的时间复杂度为O(1),但是如果查找上一个结点的最坏的时间复杂度就得是O(n),因为我们每次都得从头遍历。为了解决这一问题,我们就得了解双向链表的相关特性。本篇博客我们就来说一下双向链表的有关知识,以及双向链表的相关实现。
一、双向链表的定义
双向链表是在单链表的每个结点中在设置一个指向其前驱结点的指针。所以双向链表中就会有两个指针域,一个指向其直接前驱,一个指向其直接后继。如下图所示:
二、双向链表的基本实现
1、双向链表的结构设计
typedef int ElemType;
typedef struct Node
{
ElemType data;
struct Node* prev;
struct Node* next;
}Node,*DPlist;
2、初始化(init)
初始化也就是将链表的前后指针都置为空
void init(DPlist plist)
{
if(plist == NULL)
{
exit(0);
}
plist -> prev = NULL;
plist -> next = NULL;
}
3、头插(insertHead)
在进行头插前,我们先要购买一个新的结点,便于我们的操作。购买结点时需要动态开辟一个新结点,并将它的指针域和数据域全部置为空。在进行头插是我们需要注意的是这个语句plist->next->prev = pnewnode;
我们需要先对plist->next
判断是否为空。只有它不为空时才能进行此步的操作,否则程序会崩溃。
代码实现:
Node* buyNode(ElemType val)
{
Node* pnewnode = (Node*)malloc(sizeof(Node));
pnewnode->prev = pnewnode ->next = NULL;
pnewnode->data = NULL;
return pnewnode;
}
int insertHead(DPlist plist,ElemType val)
{
Node* pnewnode = buyNode(val);
pnewnode->next =plist ->next;
pnewnode->prev =plist;
if(plist->next !=NULL )
{
plist->next->prev = pnewnode;
}
plist->next = pnewnode;
return 1;
}
4、尾插(insertTail)
尾插和头插一样都要先进行结点的购买。我们在前面已经书写了这个程序,这个时候只要调用即可。尾插相对于头插来说较为简单。重点理解这两句语句:
pTail->next = pnewnode;
pnewnode->prev = pTail;
代码实现:
int insertTail(DPlist plist,ElemType val)
{
Node* pnewnode = buyNode(val);
Node* pTail = plist;
while(pTail->next)
{
pTail = pTail->next;
}
pTail->next = pnewnode;
pnewnode->prev = pTail;
return 1;
}
5、按位插入(insertPos)
按位插入之前我们必须先要知道此链表当前插入位置的长度。这个时候我们就需要再写一个getLength函数来得到当前位置的长度。此外,按位插入和头插一样也需要对pfront->next
进行判空。只有在不为空的情况下才可以进行pfront->next->prev = pnewnode
int getLength(DPlist plist)
{
int count = 0;
Node* pCur = plist->next;
while(pCur != NULL)
{
count ++;
pCur = pCur->next;
}
return count;
}
int insertPos(DPlist plist,int pos,ElemType val)
{
Node* pfront = plist;
int i = 0;
if(pos < 0 || pos > getLength(plist))
{
return 0;
}
for(i;i<pos;i++)
{
pfront = pfront ->next;
}
Node* pnewnode = buyNode(val);
pnewnode->next = pfront->next;
pnewnode->prev = pfront;
if(pfront->next !=NULL)
{
pfront->next->prev = pnewnode;
}
pfront->next = pnewnode;
return 1;
}
6、头删(deleteHead)
我们先要对链表进行判空的处理,如果链表为空。return 0;如果不为空,将所要删除结点的下一结点,赋值给头结点的下一节点,再将所删结点的前驱指针指向头结点。也就是这两句语句的重点理解
plist->next = pCur->next;
pCur->next->prev = plist;
需要注意的是对pCur->next有个是否为空的判断。
代码实现
int empty(DPlist plist)
{
return plist->next == NULL ? 1:0;
}
int deleteHead(DPlist plist)
{
if(empty(plist))
{
return 0;
}
Node* pCur = plist->next;
plist->next = pCur->next;
if(pCur->next !=NULL)
{
pCur->next->prev = plist;
}
free (pCur);
return 1;
}
7、尾删(deletdTail)
和头删一样都需要先进行判空的处理。具体代码实现如下:
int deletdTail(DPlist plist)
{
if(empty(plist))
{
return 0;
}
Node* ptail2 = plist;
Node* pCur = NULL;
while(ptail->next != NULL)
{
if(ptail2->next->prev == NULL)
{
break;
}
ptail2 = ptail2->next;
}
pCur = ptail ->next;
free(pCur);
return 1;
}
8、按位删除(deletePos)
我们先要得到删除位置的链表的长度。需要判断的是如果此位置<0或者超出此位置的长度,则return 0;具体代码实现如下:
int deletePos(DPlist plist,int pos)
{
if(pos<0||pos>=getLength(plist))
{
return 0;
}
Node* pfront = plist;
Node* pCur = NULL;
int i = 0;
for(i; i<0; i++)
{
pfront = pfront->next;
}
pCur = pfront->next;
pfront->next = pCur->next;
if(pCur->next !=NULL)
{
pCur->next->prev = pfront;
}
free(pCur);
return 1;
}
9、链表的摧毁(destroy)
void destroy(DPlist plist)
{
Node* pCur = plist->next;
Node* pNext = NULL;
while(pCur != NULL)
{
pNext = pCur->next;
delete pCur;
pCur = pNext;
}
plist->next = NULL;
}