双向链表
1. 特性
逻辑结构:线性结构
存储结构:链式存储
操作:增删改查
创空:
插入:
删除:
按数据删除:
#include <stdio.h>
#include <stdlib.h>
typedef int datatype;
typedef struct node
{
datatype data; //数据域
struct node *next; //指向下一个节点的指针
struct node *prior; //指向前一个人节点的指针
} node_t, *node_p;
typedef struct doublelinklist
{
node_p head; //指向双向链表的头指针
node_p tail; //指向双向链表的尾指针
int len; //保存双向链表的长度
} doublelinklist_t, *doublelinklist_p;
//将头尾指针以及长度封装的一个结构体里面用来操作整个双向链表
//方法上有点像链式队列
//创建一个空双向链表
doublelinklist_p createEmptyDoubleLinkList()
{
//1.开辟双向链表结构体大小空间
doublelinklist_p p = (doublelinklist_p)malloc(sizeof(doublelinklist_t));
if (NULL == p)
{
perror("p malloc err");
return NULL;
}
//2. 初始化结构体空间
p->len = 0;
p->head = p->tail = (node_p)malloc(sizeof(node_t));
if (NULL == p->head)
{
perror("p->head malloc err");
return NULL;
}
//3.初始化头节点, 前驱和后继指针都置空
p->head->next = NULL;
p->head->prior = NULL;
return p;
}
向双向链表的指定位置插入数据 post位置, data数据
int insertIntoDoubleLinkList(doublelinklist_p p, int post, datatype data)
{
node_p temp = NULL;
//1.容错判断
if (post < 0 || post > p->len)
{
printf("insertIntoDoubleLinkList err\n");
return -1;
}
//2. 创建新节点保存数据
node_p pnew = (node_p)malloc(sizeof(node_t));
if (NULL == pnew)
{
perror("pnew malloc err");
return -1;
}
pnew->data = data;
pnew->next = NULL;
pnew->prior = NULL;
//3.将节点插入到链表,需要分情况讨论
if (post == p->len) //需要尾插
{
//连接
p->tail->next = pnew;
pnew->prior = p->tail;
//移动尾指针到新节点
p->tail = pnew;
}
else //中间插入,需要判断前后半段
{
//将temp移动到插入位置
if (post < p->len / 2) //前半段
{
temp = p->head;
for (int i = 0; i <= post; i++)
temp = temp->next;
}
else
{
temp = p->tail;
for (int i = p->len - 1; i > post; i--)
temp = temp->prior;
}
//将节点连接到链表
pnew->prior = temp->prior;
temp->prior->next = pnew;
pnew->next = temp;
temp->prior = pnew;
}
p->len++;
return 0;
}
//遍历双向链表
void showDoubleLinkList(doublelinklist_p p)
{
node_p temp = NULL;
printf("正向遍历:\n");
temp = p->head;
while (temp->next != NULL) //相当于遍历有头链表
{
temp = temp->next;
printf("%d ", temp->data);
}
printf("\n");
printf("反向遍历:\n");
temp = p->tail;
while (temp != p->head) //相当于遍历无头链表
{
printf("%d ", temp->data);
temp = temp->prior;
}
printf("\n");
}
//判断双向链表是否为空
int isEmptyDoubleLinkList(doublelinklist_p p)
{
return p->len == 0;
}
删除双向链表指定位置数据
int deletePostDoubleLinkList(doublelinklist_p p, int post)
{
node_p pdel = NULL;
//1. 容错判断
if (isEmptyDoubleLinkList(p) || post < 0 || post >= p->len)
{
printf("deletePostDoubleLinkList err\n");
return -1;
}
//2.删除节点,分删除尾节点或删除中间节点
if (post == p->len - 1) //删除尾节点
{
p->tail = p->tail->prior;
free(p->tail->next);
p->tail->next = NULL;
}
else //删除中间节点,需要判断前后半段
{
//1). 将pdel移动到删除节点
if (post < p->len / 2) //前半段
{
pdel = p->head;
for (int i = 0; i <= post; i++)
pdel = pdel->next;
}
else //后半段
{
pdel = p->tail;
for (int i = p->len - 1; i > post; i--)
pdel = pdel->prior;
}
//2). 跨过要删除节点
pdel->next->prior = pdel->prior;
pdel->prior->next = pdel->next;
free(pdel);
}
//3. 让长度减一
p->len--;
return 0;
}
//删除双向链表中的指定数据 data代表删除所有出现的data数据
/*思想:从头节点后节点开始用指针h遍历,相当于遍历无头链表,遇到需要删除节点的就用h指向它然后删除,如果不需要删除则h继续往后走一个。这里因为是双向链表可以找到前驱,所以不需要每次指向被删除节点的前一个然后跨过了。
*/
void deleteDataDoubleLinkList(doublelinklist_p p, datatype data)
{
node_p pdel = NULL; //用于指向删除节点
node_p h = p->head->next; //跨越头节点
while (h != NULL) //相当于用h遍历无头链表
{
if (h->data == data) //如果相等,就删除节点
{
//删除操作, 分删除尾节点还是中间节点。
if (h == p->tail) //删除尾节点
{
//(1) 将尾指针向前移动一个单位
p->tail = p->tail->prior;
// (2) 释放最后一个节点
free(p->tail->next);
//(3) 此时尾节点的next置空
p->tail->next = NULL;
}
else //删除中间节点
{
//(1) pdel记录要删除节点
pdel = h;
//(2) 前后跨过要删除节点
h->prior->next = h->next;
h->next->prior = h->prior;
//(3) 让h向后移动一个
h = h->next;
//(4) 释放要删除节点
free(pdel);
}
p->len--;
}
else //否则继续向后遍历
{
h = h->next;
}
}
}
//查找指定数据出现的位置 data被查找的数据, 找到返回下标,没找到返回-1
int searchPostDoubleLinkList(doublelinklist_p p, datatype data)
{
node_p h = p->head;
int post = 0;
while (h->next != NULL)
{
h = h->next;
if (h->data == data)
return post;
post++;
}
return -1;
}
//修改指定位置的数据,post修改的位置 data被修改的数据
int changeDataDoubleLinkList(doublelinklist_p p, int post, datatype data)
{
//1. 容错判断
if (isEmptyDoubleLinkList(p) || post < 0 || post >= p->len)
{
printf("changeDataDoubleLinkList err\n");
return -1;
}
//将指针移动到要修改节点处
node_p temp = NULL;
if (post < p->len / 2) //前半段
{
temp = p->head;
for (int i = 0; i <= post; i++)
temp = temp->next;
}
else
{
temp = p->tail;
for (int i = p->len - 1; i > post; i--)
temp = temp->prior;
}
//3.修改节点中数据
temp->data = data;
return 0;
}
int main(int argc, char const *argv[])
{
doublelinklist_p p = createEmptyDoubleLinkList();
insertIntoDoubleLinkList(p, 0, 1);
insertIntoDoubleLinkList(p, 1, 2);
insertIntoDoubleLinkList(p, 2, 3);
insertIntoDoubleLinkList(p, 0, 100);
showDoubleLinkList(p); //100 1 2 3
//changeDataDoubleLinkList(p, 3, 50);
printf("2 post is: %d\n", searchPostDoubleLinkList(p, 2)); //2 post is: 2
deletePostDoubleLinkList(p, 0);
deletePostDoubleLinkList(p, 2);
showDoubleLinkList(p); //1 2
insertIntoDoubleLinkList(p, 2, 1);
showDoubleLinkList(p); //1 2 1
deleteDataDoubleLinkList(p, 1);
showDoubleLinkList(p); //2
return 0;
}
2. 双向循环链表
#include <stdio.h>
#include <stdlib.h>
typedef int datatype;
typedef struct node_t
{
datatype data;
struct node_t * prior;
struct node_t * next;
}link_node_t,*link_node_p;
typedef struct doublelinklist
{
link_node_p head;
link_node_p tail;
}double_list_t,*double_list_p;
int main(int argc, const char *argv[])
{
int i;
int all_num = 8;//猴子总数
int start_num = 3;//从3号猴子开始数
int kill_num = 3;//数到几杀死猴子
link_node_p h = NULL;
link_node_p pdel = NULL;//用来指向被杀死猴子的节点
printf("请您输入猴子的总数,开始号码,出局号码:\n");
scanf("%d%d%d",&all_num,&start_num,&kill_num);
//1.创建一个双向的循环链表
double_list_p p = (double_list_p)malloc(sizeof(double_list_t));//申请头指针和尾指针
if(NULL == p)
{
perror("malloc failed");
return -1;
}
p->head = p->tail = (link_node_p)malloc(sizeof(link_node_t));
if(NULL == p->tail)
{
perror("p->tail malloc failed");
return -1;
}
p->head->data = 1;
p->head->prior = NULL;
p->head->next = NULL;
//将创建n个新的节点,链接到链表的尾
for(i = 2; i <= all_num; i++)
{
link_node_p pnew = (link_node_p)malloc(sizeof(link_node_t));
if(NULL == pnew)
{
perror("pnew malloc failed");
return -1;
}
pnew->data = i;
pnew->prior = NULL;
pnew->next = NULL;
//(1)将新的节点链接到链表的尾
p->tail->next = pnew;
pnew->prior = p->tail;
//(2)尾指针向后移动,指向当前链表的尾
p->tail = pnew;
}
//(3)形成双向循环链表
p->tail->next = p->head;
p->head->prior = p->tail;
//调试程序
#if 0
while(1)
{
printf("%d\n",p->head->data);
p->head = p->head->next;
sleep(1);
}
#endif
//2.循环进行杀死猴子
h = p->head;
//(1)先将h移动到start_num处,也就是开始数数的猴子号码处
for(i = 1; i < start_num; i++)
h = h->next;
printf("start is:%d\n",h->data);
while(h->next != h)//当h->next == h 就剩一个节点了,循环结束
{
//(2)将h移动到即将杀死猴子号码的位置
for(i = 1; i < kill_num; i++)
h = h->next;
//(3)进行杀死猴子,经过上面的循环后,此时的h指向即将杀死的猴子
h->prior->next = h->next;
h->next->prior = h->prior;
pdel = h;//pdel指向被杀死猴子的位置
printf("kill is -------%d\n",pdel->data);
h = h->next;//需要移动,从杀死猴子后的下一个位置开始数
free(pdel);
}
printf("猴王是%d\n",h->data);
return 0;
}
面试题:单向链表与双向(循环)链表的区别:
在存储空间方面:单链表需要的存储空间比双向链表的要少,因为双向链表不仅保存有指向下一个节点的指针,还保存有指向上一个节点的指针,需要较大的空间来存储双向链表的指针域。
在处理时间方面:双向链表的插入与删除操作比单链表的效率高,因为如果在后半段删除或者插入可以从后往前遍历到插入或删除位置然后进行操作。
请完成如下双向链表的删除过程: (云尖软件开发笔试试题)
A. p-> next=p->prior->next;
p->prior=p->next->prior;
free(p);
B. p->prior = p->next;
p->nest = p->prior;
free(p);
C. p->prior->next = p->next;
p->next >prior = p->prior:
free(p);
D. p->prior->next = p->next->prior;
p->next->prior=p->prior->next;