双向链表的设计与实现:
相比于单向链表,双向链表在指针域多出一个用于存储上一个结点的前驱指针这样的好处使得在结点插入或者删除时,我们就无需去查找/获取待操作结点的上一个结点了,因为在单向链表中结点插入或者删除,受形响的结点往往是上一 个结点。
插入删除不需要移动元素外,可以原地插入删除
可以双向遍历
1.双向链表的组成:
1.1 头指针(指向头结点的指针变量)
1.2节点/结点//双向链表
1) 数据域
2) 指针域
结构体:
//双向链表
typedef int data_t;
typedef struct linklist
{
data_t data;
struct linklist* prev;
struct linklist* next;
}linklist_t;
创建链表:创建一个节点,返回节点地址
//创建节点 输出型参数返回地址(指针)
int list_create(linklist_t** head,data_t data) //一级指针的时候此时head还是值传递 所以需要用二级指针对head指针的地址进行传递
{
linklist_t *p = (linklist_t*)malloc(sizeof(linklist_t)); //申请一个节点空间
if (p == NULL)
return -1;
p->data = data;
p ->next = NULL;
p->prev = NULL;
*head = p; //通过对头指针解引用
return p;
}
增加数据:
1.头插法
2.中间插法
3.尾插法
1.头插法
//在头结点插入元素(头插法)
int list_addhead(linklist_t** head, data_t data)
{
linklist_t* p = (linklist_t*)malloc(sizeof(linklist_t));
if (p == NULL)
return -1;
p->data = data;
p->next = p ->prev = NULL; //将要插入数据节点的后驱指向头结点的地址
p->next = *head; //通过对头指针解引用
if (*head)
(*head)->prev = p; //->的优先级大于*
*head = p;
return 0;
}
2.中间插法:需要考虑:1.是否是空链表 2.插入的位置是否是头节点 3.插入的位置是否是尾节点 4.中间插入数据 5.若要插入的数据未找到插入位置,则插入到链表的后面
//中间插法
int list_insert(linklist_t** head, data_t newdata,data_t olddata)
{
linklist_t* pnew = (linklist_t*)malloc(sizeof(linklist_t));
if (pnew == NULL)
return -1;
pnew->data = newdata;
pnew->next = pnew->prev = NULL;
linklist_t* p = *head,*q=NULL;
if (p == NULL) //链表为空,用新数据创建链表
{
*head = pnew;
return 0;
}
if (memcmp(&p->data, &olddata, sizeof(data_t)) == 0) //插入的位置为头节点位置
{
pnew->next = p;
p->prev = pnew;
*head = pnew;
return 0;
}
while (p)
{
if (memcmp(&p->data, &olddata, sizeof(data_t)) == 0) //插入到链表中的某节点位置
{
pnew->next = p;
pnew->prev = p->prev;
p->prev->next = pnew;
p->prev = pnew;
return 0;
}
q = p; //使用快慢指针
p = p->next;
}
//未找到插入位置,将新节点尾插到链表中
q->next = pnew;
pnew->prev = q;
return 0;
}
3.尾插法
//在链表尾部插入数据(尾插法)
int list_addtail(linklist_t** head, data_t data)
{
linklist_t* pnew = (linklist_t*)malloc(sizeof(linklist_t));
if (pnew == NULL )
return -1;
pnew->data = data;
pnew->next = pnew->prev = NULL;
linklist_t* p = *head,*q=NULL; //利用快慢指针 要对head解引用
if (p == NULL)
{
*head = pnew; //p为空,则head链表是空链表,所以将要插入的节点就为头节点
return 0;
}
//遍历链表
while (p->next)
{
p = p->next;
}
p->next = pnew;
pnew->prev = p;
return 0;
}
删除数据:需要考虑1.是否是空链表 2.该数据是否在链表中 3.删除的数据若为头节点则需考虑头节点是否是唯一的节点 ①是:p->next=NULL ②不是:p->next=p->prev=NULL; head=p->next; 4.如果删除的不是头节点则遍历链表找到要删除的数据,还要考虑是否删除的是尾节点p-next是否为NULL,①是:p->next->prev=NULL;②不是:p->next->prev=p->prev;
//删除节点
int list_delete(linklist_t** head, data_t data)
{
linklist_t* p = *head;
if (p == NULL)
return -1;
if (memcmp(&p->data, &data, sizeof(data_t)) == 0) //删除的是头节点
{
if (p->next == NULL) //头节点是唯一的节点
{
*head = NULL;
}
else //头节点不是唯一的节点
{
p->next->prev = NULL;
*head = p->next;
}
free(p);
return 0;
}
while (p) //进入循环后表示删除的不是头节点
{
if (memcmp(&p->data, &data, sizeof(data_t)) == 0) //数据找到
{
p->prev->next = p->next;
if(p->next) //删除的不是尾节点
p->next->prev = p->prev;
else //删除的是尾节点
{
p->prev->next = NULL;
}
free(p);//删除
return 0;
}
p = p->next;
}
return -1;
}
修改数据(与单向链表一样)
//修改数据
int list_updata(linklist_t** head, data_t olddata,data_t newdata)
{
linklist_t* p = *head;
if (p == NULL)
return -1;
while (p)
{
if (memcmp(&p->data, &olddata, sizeof(data_t)) == 0)
{
p->data = newdata;
return 0;
}
p = p->next;
}
return -1;
}
查找数据(与单向链表一样)
//查找数据
linklist_t* list_find(linklist_t* head, data_t data)
{
linklist_t* p = head;
if (p == NULL)
return -1;
while (p)
{
if (memcmp(&p->data, &data, sizeof(data_t)) == 0)
{
printf("找到了%d\n", data);
return 0;
}
p = p->next;
}
return -1;
}
遍历链表(与单向链表一样)
//遍历链表
int list_showall(linklist_t* head) //不涉及对头指针地址的修改,所以用一级指针即可
{
linklist_t* p = head;
if (p == NULL)
return -1;
while (p)
{
printf("%d ", p->data);
p = p->next;
}
printf("\n");
return 0;
}
回收链表(与单向链表一样)
//回收链表
int list_free(linklist_t** head)
{
linklist_t* p = *head, * q = NULL;
while (p)
{
q = p;
p = p->next;
free(q);
}
*head = NULL;
return 0;
}
附完整代码:
linklist.h
#ifndef __LINKLIST_H //预处理用于条件编译 避免头文件反复包含
#define __LINKLIST_H
//双向链表
typedef int data_t;
typedef struct linklist
{
data_t data;
struct linklist* prev;
struct linklist* next;
}linklist_t;
int list_create(linklist_t** head, data_t data);
int list_addhead(linklist_t** head, data_t data);
int list_insert(linklist_t** head, data_t newdata, data_t olddata);
int list_addtail(linklist_t** head, data_t data);
int list_updata(linklist_t** head, data_t olddata, data_t newdata);
linklist_t* list_find(linklist_t* head, data_t data);
int list_delete(linklist_t** head, data_t data);
int list_showall(linklist_t* head);
int list_free(linklist_t** head);
#endif
linklist.c
#include "linklist.h"
#include<stdio.h>
#include<stdlib.h>
//创建节点 输出型参数返回地址(指针)
int list_create(linklist_t** head,data_t data) //一级指针的时候此时head还是值传递 所以需要用二级指针对head指针的地址进行传递
{
linklist_t *p = (linklist_t*)malloc(sizeof(linklist_t)); //申请一个节点空间
if (p == NULL)
return -1;
p->data = data;
p ->next = NULL;
p->prev = NULL;
*head = p; //通过对头指针解引用
return p;
}
//在头结点插入元素(头插法)
int list_addhead(linklist_t** head, data_t data)
{
linklist_t* p = (linklist_t*)malloc(sizeof(linklist_t));
if (p == NULL)
return -1;
p->data = data;
p->next = p ->prev = NULL; //将要插入数据节点的后驱指向头结点的地址
p->next = *head; //通过对头指针解引用
if (*head)
(*head)->prev = p; //->的优先级大于*
*head = p;
return 0;
}
//中间插法
int list_insert(linklist_t** head, data_t newdata,data_t olddata)
{
linklist_t* pnew = (linklist_t*)malloc(sizeof(linklist_t));
if (pnew == NULL)
return -1;
pnew->data = newdata;
pnew->next = pnew->prev = NULL;
linklist_t* p = *head,*q=NULL;
if (p == NULL) //链表为空,用新数据创建链表
{
*head = pnew;
return 0;
}
if (memcmp(&p->data, &olddata, sizeof(data_t)) == 0) //插入的位置为头节点位置
{
pnew->next = p;
p->prev = pnew;
*head = pnew;
return 0;
}
while (p)
{
if (memcmp(&p->data, &olddata, sizeof(data_t)) == 0) //插入到链表中的某节点位置
{
pnew->next = p;
pnew->prev = p->prev;
p->prev->next = pnew;
p->prev = pnew;
return 0;
}
q = p; //使用快慢指针
p = p->next;
}
//未找到插入位置,将新节点尾插到链表中
q->next = pnew;
pnew->prev = q;
return 0;
}
//在链表尾部插入数据(尾插法)
int list_addtail(linklist_t** head, data_t data)
{
linklist_t* pnew = (linklist_t*)malloc(sizeof(linklist_t));
if (pnew == NULL )
return -1;
pnew->data = data;
pnew->next = pnew->prev = NULL;
linklist_t* p = *head,*q=NULL; //利用快慢指针 要对head解引用
if (p == NULL)
{
*head = pnew; //p为空,则head链表是空链表,所以将要插入的节点就为头节点
return 0;
}
//遍历链表
while (p->next)
{
p = p->next;
}
p->next = pnew;
pnew->prev = p;
return 0;
}
//修改数据
int list_updata(linklist_t** head, data_t olddata,data_t newdata)
{
linklist_t* p = *head;
if (p == NULL)
return -1;
while (p)
{
if (memcmp(&p->data, &olddata, sizeof(data_t)) == 0)
{
p->data = newdata;
return 0;
}
p = p->next;
}
return -1;
}
//查找数据
linklist_t* list_find(linklist_t* head, data_t data)
{
linklist_t* p = head;
if (p == NULL)
return -1;
while (p)
{
if (memcmp(&p->data, &data, sizeof(data_t)) == 0)
{
printf("找到了%d\n", data);
return 0;
}
p = p->next;
}
return -1;
}
//删除节点
int list_delete(linklist_t** head, data_t data)
{
linklist_t* p = *head;
if (p == NULL)
return -1;
if (memcmp(&p->data, &data, sizeof(data_t)) == 0) //删除的是头节点
{
if (p->next == NULL) //头节点是唯一的节点
{
*head = NULL;
}
else //头节点不是唯一的节点
{
p->next->prev = NULL;
*head = p->next;
}
free(p);
return 0;
}
while (p) //进入循环后表示删除的不是头节点
{
if (memcmp(&p->data, &data, sizeof(data_t)) == 0) //数据找到
{
p->prev->next = p->next;
if(p->next) //删除的不是尾节点
p->next->prev = p->prev;
else //删除的是尾节点
{
p->prev->next = NULL;
}
free(p);//删除
return 0;
}
p = p->next;
}
return -1;
}
//回收链表
int list_free(linklist_t** head)
{
linklist_t* p = *head, * q = NULL;
while (p)
{
q = p;
p = p->next;
free(q);
}
*head = NULL;
return 0;
}
//遍历链表
int list_showall(linklist_t* head) //不涉及对头指针地址的修改,所以用一级指针即可
{
linklist_t* p = head;
if (p == NULL)
return -1;
while (p)
{
printf("%d ", p->data);
p = p->next;
}
printf("\n");
return 0;
}
main.c
#pragma warning(disable:4996)
#include "linklist.h" //不能多次重复包含 会报错
#include<stdio.h>
int main()
{
linklist_t* head = NULL;
list_create(&head,321); //接收指针的地址
list_addhead(&head, 666);//头插法
//中间插法
list_addtail(&head, 222); //尾插法
list_addtail(&head, 888);
list_addtail(&head, 999);
list_showall(head); //遍历链表
list_find(head,666);
/*while (1)
{
data_t data;
printf("请输入要删除的数据(-1退出):\n");
scanf("%d", &data);
if (data == -1)
break;
if (-1 == list_delete(&head, data))
{
printf("删除的数据不存在,请重试...\n");
continue;
}
list_showall(head); //遍历链表
}
*/
while (1)
{
#if 0 //预处理条件编译
data_t data;
printf("请输入要删除的数据(-1退出):\n");
scanf("%d", &data);
if (data == -1)
break;
if (-1 == list_delete(&head, data))
{
printf("删除的数据不存在,请重试...\n");
continue;
}
list_showall(head); //遍历链表
#else
data_t olddata,newdata;
printf("请输入要在那个数据前插入(-1退出):\n");
scanf("%d", &olddata);
if (olddata == -1)
break;
printf("请输入插入的数据(-1退出):\n");
scanf("%d", &newdata);
if (-1 == list_insert(&head, newdata, olddata))
{
printf("数据插入失败,请重试...\n");
continue;
}
list_showall(head); //遍历链表
#endif
}
list_free(&head); //回收链表
return 0;
}