四、双向线性链表
C语言中一种更复杂的链表式“双向链表”或“双面链表”。其表中的每个节点有两个连接:一个指向前一个节点,(当这个“连接”为第一个:“连接”时,指向空值或者空列表);而另一个指向下一个节点,(当这个“连接”为最后一个:“连接”时,指向空值或者空列表)。例如:
在一些低级语言中,XOR-linking 提供一种在双向链表中通过用一个词来表示两个链接(前后),我们通常不提倡这种方法.
双向链表也叫双链表。双向链表中不仅有指向后一个节点的指针,还有指向前一个节点的指针。这样就可以从任何一个节点访问前一个节点,当然也可以访问后一个节点,以至于整个链表。一般是在需要大批量的另外储存数据在链表中的位置的时候用。双向链表也可以配合下面的其他链表的扩展使用。
由于另外储存了指向链表内容的指针,并且可能会修改相邻的节点,有的时候第一个节点可能会被删除或者之前添加一个新的节点。这时候就要修改指向首个节点的指针。有一种方便的可以消除这种特殊情况的方法是在最后一个节点之后、第一个节点之前储存一个永远不会被删除或移动的虚拟节点,形成一个下面说的循环链表。这个虚拟节点之后的节点就是真正的第一个节点。这种情况通常可以用这个虚拟节点直接表示这个链表,对于把链表单独的存在数据里的情况,也可以直接用这个数组表示链表并用第 0 个或者第 -1 个 (如果编译器支持)节点固定的表示这个虚拟节点。
1、双向线性链表
#include
#include
typedef struct Node
{
int data;
struct Node *pNext;
struct Node *pPre;
}NODE, *pNODE;
//创建双向链表
pNODE CreateDbLinkList(void);
//打印链表
void TraverseDbLinkList(pNODE pHead);
//判断链表是否为空
int IsEmptyDbLinkList(pNODE pHead);
//计算链表长度
int GetLengthDbLinkList(pNODE pHead);
//向链表插入节点
int InsertEleDbLinkList(pNODE pHead, int pos, int data);
//从链表删除节点
int DeleteEleDbLinkList(pNODE pHead, int pos);
//删除整个链表,释放内存
void FreeMemory(pNODE *ppHead);
int main(void)
{
int flag = 0, length = 0;
int position = 0, value = 0;
pNODE head = NULL;
head = CreateDbLinkList();
flag = IsEmptyDbLinkList(head);
if (flag)
printf("双向链表为空! ");
else
{
length = GetLengthDbLinkList(head);
printf("双向链表的长度为:%d ", length);
TraverseDbLinkList(head);
}
printf("请输入要插入节点的位置和元素值(两个数用空格隔开):");
scanf("%d %d", &position, &value);
flag = InsertEleDbLinkList(head, position, value);
if (flag)
{
printf("插入节点成功! ");
TraverseDbLinkList(head);
}
else
printf("插入节点失败! ");
flag = IsEmptyDbLinkList(head);
if (flag)
printf("双向链表为空,不能进行删除操作! ");
else
{
printf("请输入要删除节点的位置:");
scanf("%d", &position);
flag = DeleteEleDbLinkList(head, position);
if (flag)
{
printf("删除节点成功! ");
TraverseDbLinkList(head);
}
else
printf("删除节点失败! ");
}
FreeMemory(&head);
if (NULL == head)
printf("已成功删除双向链表,释放内存完成! ");
else
printf("删除双向链表失败,释放内存未完成! ");
return 0;
}
//创建双向链表
pNODE CreateDbLinkList(void)
{
int i, length = 0, data = 0;
pNODE pTail = NULL, p_new = NULL;
pNODE pHead = (pNODE)malloc(sizeof(NODE));
if (NULL == pHead)
{
printf("内存分配失败! ");
exit(EXIT_FAILURE);
}
pHead->data = 0;
pHead->pPre = NULL;
pHead->pNext = NULL;
pTail = pHead;
printf("请输入想要创建链表的长度:");
scanf("%d", &length);
for (i=1; idata = data;
p_new->pNext = NULL;
p_new->pPre = pTail;
pTail->pNext = p_new;
pTail = p_new;
}
return pHead;
}
//打印链表
void TraverseDbLinkList(pNODE pHead)
{
pNODE pt = pHead->pNext;
printf("打印链表如:");
while (pt != NULL)
{
printf("%d ", pt->data);
pt = pt->pNext;
}
putchar(' ');
}
//判断链表是否为空
int IsEmptyDbLinkList(pNODE pHead)
{
pNODE pt = pHead->pNext;
if (pt == NULL)
return 1;
else
return 0;
}
//计算链表的长度
int GetLengthDbLinkList(pNODE pHead)
{
int length = 0;
pNODE pt = pHead->pNext;
while (pt != NULL)
{
length++;
pt = pt->pNext;
}
return length;
}
//向双向链表中插入节点
int InsertEleDbLinkList(pNODE pHead, int pos, int data)
{
pNODE pt = NULL, p_new = NULL;
if (pos > 0 && pos < GetLengthDbLinkList(pHead)+2)
{
p_new = (pNODE)malloc(sizeof(NODE));
if (NULL == p_new)
{
printf("内存分配失败! ");
exit(EXIT_FAILURE);
}
while (1)
{
pos--;
if (0 == pos)
break;
pHead = pHead->pNext;
}
pt = pHead->pNext;
p_new->data = data;
p_new->pNext = pt;
if (NULL != pt)
pt->pPre = p_new;
p_new->pPre = pHead;
pHead->pNext = p_new;
return 1;
}
else
return 0;
}
//从链表中删除节点
int DeleteEleDbLinkList(pNODE pHead, int pos)
{
pNODE pt = NULL;
if (pos > 0 && pos < GetLengthDbLinkList(pHead) + 1)
{
while (1)
{
pos--;
if (0 == pos)
break;
pHead = pHead->pNext;
}
pt = pHead->pNext->pNext;
free(pHead->pNext);
pHead->pNext = pt;
if (NULL != pt)
pt->pPre = pHead;
return 1;
}
else
return 0;
}
//删除整个链表,释放内存
void FreeMemory(pNODE *ppHead)
{
pNODE pt = NULL;
while (*ppHead != NULL)
{
pt = (*ppHead)->pNext;
free(*ppHead);
if (NULL != pt)
pt->pPre = NULL;
*ppHead = pt;
}
}
输出结果:
请输入想要创建链表的长度:5
请输入第1个元素的值:1
请输入第2个元素的值:2
请输入第3个元素的值:3
请输入第4个元素的值:4
请输入第5个元素的值:5
双向链表的长度为:5
打印链表如:1 2 3 4 5
请输入要插入节点的位置和元素值(两个数用空格隔开):2 6
插入节点成功!
打印链表如:1 6 2 3 4 5
请输入要删除节点的位置:2
删除节点成功!
打印链表如:1 2 3 4 5
已成功删除双向链表,释放内存完成!
2、自写双向线性链表
代码实现说明:
插入节点时,首先判断要插入的节点的位置是否正确,然后创建节点。最后,插入节点,插入时分两种情况处理,一种是插入到第一个节点前面,另一种是插入到链表中间。
创建持有待插入元素的新节点,令其前指针指向给定节点的前节点,令其后指针指向给定节点。
若新建节点存在前节点,则令其前节点的后指针指向新建节点,否则新建节点为新的首节点。
令新建节点的后节点的前指针指向新建节点。
删除节点时,首先判断要删除的结点位置是否正确。然后,删除结点,删除时分两种情况处理:一种是删除第一个结点,另一种是删除链表的中间结点。最后,释放被删除结点所占有的存储空间。
若将亡节点存在前节点,则令其前节点的后指针指向将亡节点的后节点,否则将亡节点的后节点为新的首节点。
若将亡节点存在后节点,则令其后节点的前指针指向将亡节点的前节点,否则将亡节点的前节点为新的尾节点。
销毁将亡节点。
#include
#include
#include
typedef int DataType;
struct Node{
DataType data;
struct Node *pre, *next;
};
void init(struct Node** head)
{
*head = NULL;
}
int getSize(struct Node* head)
{
struct Node* p = head;
int count = 0;
while(p)
{
count ++;
p = p->next;
}
return count;
}
//找到指定位置 元素地址
struct Node* getptr(struct Node* head, int pos) {
struct Node *p = head;
if (p == 0 || pos == 0) {
return head;
}
int i = 0;
for(i = 0; p && i < pos; i++) {
p = p->next;
}
return p;
}
//指定位置 插入元素
bool insert(struct Node** head, int position, DataType d) {
if (position < 0 || position > getSize(*head)) {
return false;
}
//创建 节点
struct Node *node = (struct Node*)malloc(sizeof(struct Node));
node->data = d;
node->pre = NULL;
node->next = NULL;
//插入到第一个节点的前面
if (position == 0) {
node->next = *head;
if (*head != NULL)
(*head)->pre = node;
*head = node;
return true;
}
//插入到链表的中间
struct Node *p = getptr(*head, position - 1);
struct Node* r = p->next;
node->next = r;
r->pre = node;
p->next = node;
node->pre = p;
return true;
}
//删除指定位置元素
bool erases(struct Node** head, int pos) {
if (pos < 0 || pos >= getSize(*head))
return false;
//删除第一个结点
struct Node *p = *head;
if (pos == 0) {
*head = (*head)->next;
if (*head != NULL)
(*head)->pre = NULL;
free(p);
p = NULL;
return true;
}
//删除链表的中间结点
p = getptr(*head, pos - 1);
struct Node *q = p->next;
p->next = q->next;
q->next->pre = p;
free(q);
q = NULL;
return true;
}
//修改指定位置 元素
bool set(struct Node* head, int pos, DataType d) {
if (pos < 0 || pos >= getSize(head)) {
return false;
}
struct Node *p = getptr(head, pos);
p->data = d;
return true;
}
//清理 链表
void clears(struct Node* head) {
while (head) {
struct Node *p = head->next;
free(head);
head = p;
}
}
//打印
void print(struct Node* head) {
struct Node *p = head;
while (p) {
printf("%d ", p->data);
p = p->next;
}
printf(" ");
}
int main()
{
//头指针
struct Node *headList;
init(&headList);
insert(&headList, 0, 10);
insert(&headList, 0, 20);
insert(&headList, 0, 30);
insert(&headList, 2, 40);
insert(&headList, 2, 50);
insert(&headList, 0, 60);
insert(&headList, 0, 80);
print(headList);
erases(&headList, 1);
print(headList);
set(headList, 0, 100);
set(headList, 0, 110);
print(headList);
return 0;
}
输出结果:
80 60 30 20 50 40 10
80 30 20 50 40 10
110 30 20 50 40 10
3、双向线性链表总结
每个节点除了存放元素数据之外,还需要保存指向下一个节点的指针,即所谓后指针,以及指向前一个节点的指针,即所谓前指针。
链表首节点的前指针和尾节点的后指针俱为空指针。