双链表是什么
双向链表,又称为双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。
我们根据名字来判断双链表有两个链,也就是两个指针,两个指针分别指向当前节点的下一个链表以及上一个链表,就像下面这张图
双链表与单链表的区别
单链表相比双链表的随机访问性较差,不能随机查找,只能从第一个节点开始往后遍历,查找效率低下,单链表只能单向读取。
双链表随机访问性较单链表较高,可以随机查找,因为一个节点里面有两个指针分别指向上一个和下一个节点,双链表可以双向读取,灵活性较高。
就像下面这个结构体,这个就是双链表的一个节点里面的内容,存放两个地址,一个是前一个节点,另一个是后一个节点。
struct listnode
{LTdatatype data;struct listnode* next;struct listnode* pervious;
};
带头双链表
所谓带头双链表,带头链表⾥的头节点,实际为“哨兵位”,哨兵位节点不存储任何有效元素,只是站在这⾥“放哨的,这样做的目的是为了防止遍历链表出现死循环,以头节点当作一个哨兵位,当循环遍历链表的时候遇到哨兵位就停止。
这里篇文章我们主要讲带头双链表的实现
带头双链表的代码实现
单个节点申请空间
listnode* creative_space(LTdatatype x)
{listnode* newnode = (listnode*)malloc(sizeof(listnode));if (newnode == NULL){perror("malloc");exit(1);}newnode->data = x;newnode->next = newnode->pervious = newnode;return newnode;
}
这里我们用了malloc函数来申请空间,并且用了一个if判断语句来判断一下空间有没有申请成功。这里着重来讲一下这段代码
newnode->next = newnode->pervious = newnode;
我们想一下,我们要创建的是一个带头循环双链表所以我们在创建单个节点申请空间的时候需要把上一个节点的地址和下一个节点的地址都指向自己,这样才能形成一个环,这是一种初始化策略,有一个明确的状态,他既不是上一个节点的连续,也不是下一个节点的先导,来保证创建的新空间在还没有加入链表之前是独立的,同时也符合环形链表这个概念,而不是给节点赋值给NULL,这样就是不是循环了。
初始化链表
listnode* listnode_init()
{listnode* phead = creative_space(-1);return phead;
}
这里的初始化就是创建一个哨兵位,我们就直接调用的是申请节点空间,里面的参数我们传了一个-1这样做的目的是为了,创建一个哨兵位,一个带头节点,里面存放-1,我们假设双向链表里面存放的都是正数,我们放一个负数进去可以很好的分辨出哨兵位节点,而这个哨兵位节点是个自循环的。
尾插入
void listnode_push_back(listnode* phead, LTdatatype x)
{assert(phead);listnode* newnode = creative_space(x);newnode->pervious = phead->pervious;newnode->next = phead;phead->pervious->next = newnode;phead->pervious = newnode;
}
尾插画图思路
我们为了思路清晰,首先不动链表,先让新申请的空间与链表相连接手拉手起来,在去改变原来的尾节点的指向,指向我们新申请的空间 phead->pervious->next = newnode;就这一步,我们要先找到原来的尾节点,也就是phead->pervious在去改变他的下一个指向的地址,改变成新的节点,也就是pervious->next = newnode;然后在把头节点的上一个节点指向为新申请的空间。
头插入
void listnode_pushfront(listnode* phead, LTdatatype x)
{assert(phead);listnode* newnode = creative_space(x);newnode->next = phead->next;newnode->pervious = phead;phead->next->pervious = newnode;phead->next = newnode;
}
头插相比尾插要简单一些,因为不用改变原链表的尾节点和头节点的相连。
还是先不动原链表,先让新空间与原链表进行相连,在去改原链表的指向,与新节点相连。
newnode->next = phead->next;
newnode->pervious = phead;
这两行代码对应的就是图中绿色的标识。
phead->next->pervious = newnode;
phead->next = newnode;
这两行对应的是图中棕色的标识。
尾删除
void del_back(listnode* phead)
{assert(phead && phead->next != phead);listnode* del_temp = phead->pervious;listnode* new_back = del_temp->pervious;new_back->next = phead;phead->pervious = del_temp->pervious;free(del_temp);del_temp = NULL;
}
这里我们断言了一下头节点和头节点的下一个节点指向的是不是头节点本身也就是哨兵位,哨兵位是不能删除的,我们创建了一个del_temp临时变量来存储尾节点的地址,这样方便释放尾节点。
listnode* del_temp = phead->pervious;listnode* new_back = del_temp->pervious;
这两行代码对应的是图中灰色的解释。
new_back->next = phead;
phead->pervious = del_temp->pervious;
这两行是对应绿色的解释,还有紫色的。
头删除
头删除相比尾删除简单一些我们值只需要把原始头节点存放起来,然后在把phead哨兵位和头节点的下一个节点联系起来在释放原始头节点就可以了。
void del_front(listnode* phead)
{assert(phead && phead->next != phead);listnode* del_temp = phead->next;listnode* new_front = del_temp->next;phead->next = new_front;new_front->pervious = phead;free(del_temp);del_temp = NULL;
}
这段代码对应的是图中绿色的解释
listnode* del_temp = phead->next;listnode* new_front = del_temp->next;
这段是紫色的
phead->next = new_front;new_front->pervious = phead;
查找链表
listnode* listnode_find(listnode* phead, LTdatatype x)
{listnode* pcur = phead->next;while (pcur!= phead){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}
这里我们用了遍历链表来查找元素,首先让pcur从跳过哨兵位的第一个节点开始,然后一直往后面遍历,如果找到了就返回pcur指向的节点,如果遍历链表一次后又回到了开始的起点,也就是哨兵位的后一个节点,就没有找到返回NULL。
在指定节点后面插入
下面是代码
void push_after_pos(listnode* pos, LTdatatype x)
{assert(pos);listnode* newnode = creative_space(x);newnode->next = pos->next;newnode->pervious = pos;pos->next->pervious = newnode;pos->next = newnode;
}
这里在pos节点后插入我们首先要调用查找节点函数,找到了节点之后才能插入,否则就没有办法在指定节点后插入,所以这里要断言一些pos不能为空。然后就是申请空间
删除pos节点
void del_pos(listnode* pos)
{assert(pos);pos->next->pervious = pos->pervious;pos->pervious->next = pos->next;free(pos);pos = NULL;
}
删除节点就是让删除节点的上一个节点与下一个节点的地址联系起来,在释放掉该节点就行了
释放链表空间
void del_listnode(listnode* phead)
{assert(phead);listnode* pcur = phead->next;while (pcur != phead){listnode* next = pcur->next;free(pcur);pcur = next;}free(phead);phead = NULL;
}
这里就是循环释放,注意在释放当前节点的时候要把当前节点的下一个节点的地址给存放起来,不然释放完当前地址后下一个地址就找不到了。
原码
# include<stdio.h>
# include<stdlib.h>
# include<assert.h>
typedef int LTdatatype;
struct listnode
{LTdatatype data;struct listnode* next;struct listnode* pervious;
};
typedef struct listnode listnode;listnode* listnode_init();
listnode* creative_space(LTdatatype x);
void listnode_pushfront(listnode* phead, LTdatatype x);
void listnode_push_back(listnode* phead, LTdatatype x);
void printlist(listnode* phead);
void del_back(listnode* phead);
void del_front(listnode* phead);
listnode* listnode_find(listnode* phead, LTdatatype x);
void push_after_pos(listnode* pos, LTdatatype x);
void del_pos(listnode* pos);
void del_listnode(listnode* phead);
//初始化
listnode* listnode_init()
{listnode* phead = creative_space(-1);return phead;
}
//申请空间
listnode* creative_space(LTdatatype x)
{listnode* newnode = (listnode*)malloc(sizeof(listnode));if (newnode == NULL){perror("malloc");exit(1);}newnode->data = x;newnode->next = newnode->pervious = newnode;return newnode;
}
void printlist(listnode* phead)
{assert(phead);listnode* pcur = phead->next;while (pcur != phead){printf("%d ", pcur->data);pcur = pcur->next;}
}
//尾插
void listnode_push_back(listnode* phead, LTdatatype x)
{assert(phead);listnode* newnode = creative_space(x);newnode->pervious = phead->pervious;newnode->next = phead;phead->pervious->next = newnode;phead->pervious = newnode;
}
//头插
void listnode_pushfront(listnode* phead, LTdatatype x)
{assert(phead);listnode* newnode = creative_space(x);newnode->next = phead->next;newnode->pervious = phead;phead->next->pervious = newnode;phead->next = newnode;
}
//尾删
void del_back(listnode* phead)
{assert(phead && phead->next != phead);listnode* del_temp = phead->pervious;listnode* new_back = del_temp->pervious;new_back->next = phead;phead->pervious = del_temp->pervious;free(del_temp);del_temp = NULL;
}
//头删
void del_front(listnode* phead)
{assert(phead && phead->next != phead);listnode* del_temp = phead->next;listnode* new_front = del_temp->next;phead->next = new_front;new_front->pervious = phead;free(del_temp);del_temp = NULL;
}
//查找
listnode* listnode_find(listnode* phead, LTdatatype x)
{listnode* pcur = phead->next;while (pcur!= phead){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}
//在pos之后插入
void push_after_pos(listnode* pos, LTdatatype x)
{assert(pos);listnode* newnode = creative_space(x);newnode->next = pos->next;newnode->pervious = pos;pos->next->pervious = newnode;pos->next = newnode;
}
//删除pos节点
void del_pos(listnode* pos)
{assert(pos);pos->next->pervious = pos->pervious;pos->pervious->next = pos->next;free(pos);pos = NULL;
}
void del_listnode(listnode* phead)
{assert(phead);listnode* pcur = phead->next;while (pcur != phead){listnode* next = pcur->next;free(pcur);pcur = next;}free(phead);phead = NULL;
}
void test()
{listnode* plist = listnode_init();listnode_pushfront(plist, 3);listnode_pushfront(plist, 4);listnode_push_back(plist, 44);listnode_pushfront(plist, 3);printlist(plist);printf("\n");del_back(plist);printlist(plist);del_front(plist);printf("\n");printlist(plist);listnode* find = listnode_find(plist, 3);push_after_pos(find, 55);printf("\n");printlist(plist);del_pos(find);printf("\n");printlist(plist);del_listnode(plist);plist = NULL;
}
int main()
{test();return 0;
}