一、双链表的定义
双向链表也是链表的一种,有一个数据域data,它是用来存放数据的,有两个指针域,分别指向直接前驱pre和直接后继next。 从任意一个节点开始,都可以很方便的访问它的前驱节点和后继节点。使用这种方式解决了单链表中不能使用反向遍历的问题。
二、双链表的结构
双向链表也是采用的链式存储结构 ,因此它的结构图为:
双链表节点类型D_LinkList的声明如下:
typedef int ElemType;
typedef int Status;
typedef struct D_LinkList
{
ElemType data;
struct D_LinkList* prior; //指向前驱节点
struct D_LinkList* next; //指向后继节点
}DNode, *D_List; //声明双链表类型
1.对初始化双链表
//初始化双链表
Status InitD_List(D_List& L);
实现
//初始化双链表
Status InitD_List(D_List& L)
{
L = (DNode*)malloc(sizeof(DNode)); //创建头节点
if (L==NULL)
return 0;
L->prior = NULL; //指针域置空
L->next = NULL;
return 1;
}
2.使用头插建立单链表,n表示要插入的个数
//使用头插建立单链表,n表示要插入的个数
Status CreateD_List_front(D_List& L, int n);
实现
//使用头插建立单链表,n表示要插入的个数
Status CreateD_List_front(D_List& L, int n)
{
int i;
ElemType x;
for (i = 0; i < n; i++)
{
DNode* newnode = (DNode*)malloc(sizeof(DNode)); //新建节点
cout << "请输入数据:";
cin >> x;
newnode->data = x; //保存数据
newnode->next = L->next; //使用头插创建双链表
if (L->next != NULL) //如果L不为空
L->next->prior = newnode; //将L->next的前驱prior连接到新节点newnode上
newnode->prior = L;
L->next = newnode;
}
return true;
}
3.按位查找,查找第 i 个位置的值,并用 e 返回
//按位查找,查找第 i 个位置的值,并用 e 返回
ElemType LocateElem(D_List L, int i, ElemType& e);
实现
//按位查找,查找第 i 个位置的值,并用 e 返回
ElemType LocateElem(D_List L, int i, ElemType &e)
{
DNode* p = L->next;
while(--i&&p) //遍历节点
{
p = p->next;
}
if (p== NULL) //找不到第i位置
return false;
e=p->data; //找到了,e存放数据
return true;
}
4.删除第 i 个位置的值,并用 e 返回
//删除第 i 个位置的值,并用 e 返回
Status DelElem(D_List& L, int i, ElemType& e);
实现
//删除第 i 个位置的值,并用 e 返回
Status DelElem(D_List& L, int i, ElemType& e)
{
assert(L); //判断链表是否为空
DNode* p = L;
DNode* q; //存放第i位置的前一个节点
int k = 0;
while (k<i-1 && p != NULL) //遍历节点,寻找第i位置的节点
{
k++;
p = p->next;
}
if (p == NULL) //未找到第i位置的节点
return false;
else //找到了
{
q = p->next;
if (q == NULL)
return false;
p->next = q->next; //删除节点
if (p->next != NULL) //节点不为最后一个节点时
p->next->prior = p;
e = q->data;
free(q);
return ture;
}
}
5.在第 i 个位置后插入值e
//在第 i 个位置后插入值e
Status Insert_e(D_List& L, int i, ElemType e);
实现
//在第 i 个位置后插入值e
Status Insert_e(D_List& L, int i, ElemType e)
{
DNode* p=L;
int k = 0;
while (k < i&&p!=NULL) //寻找第i位置的节点
{
k++;
p = p->next;
}
if (p->next == NULL) //未找到节点
return 0;
else //找到第i位置的节点
{
DNode* newnode = (DNode*)malloc(sizeof(DNode)); //新建节点
newnode->data = e;
newnode->next = NULL;
newnode->prior = NULL;
newnode->next = p->next; //将newnode插入第i节点之后
if (p->next != NULL) //节点p不为尾节点
p->next->prior = newnode;
p->next = newnode;
newnode->prior = p;
return ture;
}
}
三、经典习题讲解
1.有一个双链表L,设计一个算法查找第一个值为x的节点,将其与后继结点进行交换
解题思路:
先找到第一个值为x的节点p,q指向其后继节点,如下图所示,本题时将节点p移到节点q之后,实现过程时先删除节点p,再将其插入节点q之后
算法如下
//有一个双链表L,设计一个算法查找第一个值为x的节点,将其与后继结点进行交换
int swap(D_LinkList& L, ElemType x)
{
DNode* p = L.next, * q;
while (p != NULL && p->data != x) //遍历节点
p = p->next;
if (p == NULL) //未找到值未x的节点
return 0;
else //找到值为x的节点p
{
q = p->next; //q指向p的后继
if (q != NULL) //节点p不是尾节点
{
p->prior->next = q; //删除p
q->prior = p->prior;
p->next = q->next; //将节点p插入q节点之后
if (q->next != NULL)
q->next->prior = p;
q->next = p;
p->prior = q;
return 1;
}
else //节点p是尾节点,无法与后继节点交换
return 0;
}
}
2.有一个双链表L,其中有n(n>=1)个数据节点,设计一个算法将所有节点逆置
解题思路:
采用头插法建表的思路,用p遍历所有数据节点,先将新表看成只有一个头节点的双链表,然后将每个节点p插入该双链表的前端。
算法实现如下:
//有一个双链表L,其中有n(n >= 1)个数据节点,设计一个算法将所有节点逆置
void Reverse(DNode*& L)
{
DNode* p = L->next, * q;
L->next = NULL;
while (p != NULL) //遍历原双链表的所有数据节点
{
q = p->next; //用q临时保存节点p的后继节点
p->next = L->next; //将节点p插入新链表的前端
if (L->next != NULL)
L->next->prior = p;
L->next = p;
p->prior = L;
p = q; //继续处理余下的节点
}
}
总结
双向链表在插入和删除上有时效率比单链表的效果还要好,比如在某一位置前进行插入操作时只需知道这个位置,通过节点的前驱pre就更更好的进行操作,但是在应用上人们更偏向与使用单链表来实现,这时我们可以从 存储效率上 去解释这个问题。
表的前端
if (L->next != NULL)
L->next->prior = p;
L->next = p;
p->prior = L;
p = q; //继续处理余下的节点
}
}
## 总结
双向链表在插入和删除上有时效率比单链表的效果还要好,比如在某一位置前进行插入操作时只需知道这个位置,通过节点的前驱pre就更更好的进行操作,但是在应用上人们更偏向与使用单链表来实现,这时我们可以从 存储效率上 去解释这个问题。
从存储结构来看,每个双链表的节点要比单链表的节点多一个指针,而长度为n就需要 n*length(这个指针的length在32位系统中是4字节,在64位系统中是8个字节) 的空间,这在一些追求时间效率不高应用下并不适应,因为它占用空间大于单链表所占用的空间;这时设计者就会采用以时间换空间的做法,