双链表
今天我们来实现一个双链表
首先,理清结构体成员,关于结构体的命名,在当前数据结构中视为局部变量,但是为了混合编程时候的区分,我更倾向于见名知义
遵循简单明了的原则,我们叫节点为DLNode
,即Double-linked List Node
.
数据组成
- 前驱节点指针
- 数据成员
- 后继节点指针
typedef int ElemType;
typedef struct DLNode
{
DLNode *precursor;
ElemType data;
DLNode *successor;
} DLNode;
初始化
自此我们规定,在数据结构的初始化中,尽量单独定义方法去申请内存和初始化数据
使用引用形参,开辟空间然后设置指针指向为空即可
在这里,我们学习一个新的方法,可以省去我们手动置空的操作,void *calloc(size_t nitems, size_t size)
会设置分配的内存为零
- nitems – 要被分配的元素个数。
- size – 元素的大小。
void Init(DLNode *&DL)
{
DL = (DLNode *)calloc(1, sizeof(DLNode));
}
创建
首先是头插法,插入节点的操作顺序一般为(将待插入节点称为t,待插入位置的节点称为DL):
- t.data写入数据
- t后继指针指向DL的后继指针
- t前驱指针指向DL
- DL的后继指针指向t
忽然间想到了需要修改尾结点的前驱指针,然后就想到了需要判断尾结点是否为空,但是这里是创建,第一个尾结点是我们的临时节点,不可能为空,所以就不用考虑空节点的前驱问题了,这个问题是在指定位置插入时需要考虑的
void HeadInsertionCteate(DLNode *&DL)
{
DLNode *tempNode;
ElemType data;
scanf("%d", &data);
while (data != 0)
{
tempNode = (DLNode *)calloc(1, sizeof(DLNode));
tempNode->data = data;
tempNode->successor = DL->successor;
tempNode->precursor = DL;
DL->successor = tempNode;
// 头插法头结点的后继已经被初始化为空,所以省去插入的判断
// if (tempNode->successor != NULL)
// {
// //或许这里的逻辑用在插入方法里面更合适
// tempNode->successor->precursor = tempNode;
// }
scanf("%d", &data);
}
}
然后是尾插法,需要一个保持指向尾结点的游标节点
void TailInsertionCreate(DLNode *&DL)
{
DLNode *tempNode;
DLNode *tailCursor = DL; // 始终指向尾结点
int data;
scanf("%d", &data);
while (data != 0)
{
tempNode = (DLNode *)calloc(1, sizeof(DLNode));
tempNode->data = data;
tailCursor->successor = tempNode;
tempNode->precursor = tailCursor;
tailCursor = tempNode;
scanf("%d", &data);
}
// 由于是calloc方法申请内存,所以不用在此置空尾游标后继指针了
}
增加
向指定位置添加节点,那么我们不妨先去写根据位置获取节点的方法
插入的思路很简单,找到节点位置,然后有顺序的变更指向
- 使用影子节点操作内存
- 单链表不用判断尾结点是因为没有访问前驱,如果对空地址访问前驱则会出错
bool InsertByPosition(DLNode *DL, int position, ElemType data)
{
DLNode *shadowNode = GetByPosition(DL, position);
if (shadowNode == NULL) // 我还是没能实现追加,也许本来就不需要?
{
return false;
}
DLNode *tempNode = (DLNode *)calloc(1, sizeof(DLNode));
tempNode->data = data;
tempNode->successor = shadowNode;
tempNode->precursor = shadowNode->precursor;
tempNode->precursor->successor = tempNode;
if (shadowNode->successor != NULL) // 需要这个判断,如果捕捉到的是尾部节点的话
{
tempNode->successor->precursor = tempNode;
// 单链表不需要做这个判断,是因为不用获取前驱
}
return true;
}
删除
根据位置删除节点,通过我们定义查找方法,省去了做位置和合法性的判断
删除逻辑也是变更指向,需要做尾结点的判断,防止访问出错
bool DeleteByPosition(DLNode *DL, int position)
{
DLNode *designatedNode = GetByPosition(DL, position);
if (designatedNode == NULL)
{
return false;
}
designatedNode->precursor->successor = designatedNode->successor; // 尾结点也行
if (designatedNode->successor != NULL) // 需要尾结点判断,尾结点之后的节点并不存在,也不存在前驱指针
{
designatedNode->successor->precursor = designatedNode->precursor;
}
return true;
}
修改
获取指定位置的节点,修改其中数据即可
bool UpdateByPosition(DLNode *DL, int position, ElemType data)
{
DLNode *toBeUpdate = GetByPosition(DL, position);
if (toBeUpdate == NULL)
{
return false;
}
toBeUpdate->data = data;
return true;
}
查找
为了方便其他方法的使用,我们先来实现根据位置查找节点
需要判断位置的合法性
在循环里,使用严格不等号与自增运算相配合,小于的情况时,数据偏移,那么不满足时刚好为等于的情况,此时跳出循环即可
DLNode *GetByPosition(DLNode *DL, int position)
{
if (position < 0)
{
return NULL;
}
if (position == 0)
{
return DL;
}
int cursor = 1; // 以上已经省去0索引
DL = DL->successor; // 忽略头结点
while (DL != NULL)
{
if (cursor < position)
{
DL = DL->successor;
cursor += 1;
}
else
{
// 现在是cursor==position的时候,返回即可
break;
}
}
return DL;
}
这里的根据值查找也会有好几种,在这里我们只设计从前往后遍历
bool IsExist(DLNode *DL, ElemType data)
{
DL = DL->successor; // 忽略头结点
while (DL != NULL)
{
if (DL->data == data)
{
return true;
}
DL = DL->successor;
}
return false;
}
代码
#include <stdio.h>
#include <stdlib.h>
/*定义结构体*/
typedef int ElemType;
typedef struct DLNode
{
DLNode *precursor;
ElemType data;
DLNode *successor;
} DLNode;
void Init(DLNode *&DL)
{
DL = (DLNode *)calloc(1, sizeof(DLNode));
}
void HeadInsertionCteate(DLNode *&DL)
{
DLNode *tempNode;
ElemType data;
scanf("%d", &data);
while (data != 0)
{
tempNode = (DLNode *)calloc(1, sizeof(DLNode));
tempNode->data = data;
tempNode->successor = DL->successor;
tempNode->precursor = DL;
DL->successor = tempNode;
// 头插法头结点的后继已经被初始化为空,所以省去插入的判断
// if (tempNode->successor != NULL)
// {
// //或许这里的逻辑用在插入方法里面更合适
// tempNode->successor->precursor = tempNode;
// }
scanf("%d", &data);
}
}
void TailInsertionCreate(DLNode *&DL)
{
DLNode *tempNode;
DLNode *tailCursor = DL; // 始终指向尾结点
int data;
scanf("%d", &data);
while (data != 0)
{
tempNode = (DLNode *)calloc(1, sizeof(DLNode));
tempNode->data = data;
tailCursor->successor = tempNode;
tempNode->precursor = tailCursor;
tailCursor = tempNode;
scanf("%d", &data);
}
// 由于是calloc方法申请内存,所以不用在此置空尾游标后继指针了
}
DLNode *GetByPosition(DLNode *DL, int position)
{
if (position < 0)
{
return NULL;
}
if (position == 0)
{
return DL;
}
int cursor = 1; // 以上已经省去0索引
DL = DL->successor; // 忽略头结点
while (DL != NULL)
{
if (cursor < position)
{
DL = DL->successor;
cursor += 1;
}
else
{
// 现在是cursor==position的时候,返回即可
break;
}
}
return DL;
}
bool InsertByPosition(DLNode *DL, int position, ElemType data)
{
DLNode *shadowNode = GetByPosition(DL, position);
if (shadowNode == NULL) // 我还是没能实现追加,也许本来就不需要?
{
return false;
}
DLNode *tempNode = (DLNode *)calloc(1, sizeof(DLNode));
tempNode->data = data;
tempNode->successor = shadowNode;
tempNode->precursor = shadowNode->precursor;
tempNode->precursor->successor = tempNode;
if (shadowNode->successor != NULL) // 需要这个判断,如果捕捉到的是尾部节点的话
{
tempNode->successor->precursor = tempNode;
// 单链表不需要做这个判断,是因为不用获取前驱
}
return true;
}
bool DeleteByPosition(DLNode *DL, int position)
{
DLNode *designatedNode = GetByPosition(DL, position);
if (designatedNode == NULL)
{
return false;
}
designatedNode->precursor->successor = designatedNode->successor; // 尾结点也行
if (designatedNode->successor != NULL) // 需要尾结点判断,尾结点之后的节点并不存在,也不存在前驱指针
{
designatedNode->successor->precursor = designatedNode->precursor;
}
return true;
}
bool UpdateByPosition(DLNode *DL, int position, ElemType data)
{
DLNode *toBeUpdate = GetByPosition(DL, position);
if (toBeUpdate == NULL)
{
return false;
}
toBeUpdate->data = data;
return true;
}
bool IsExist(DLNode *DL, ElemType data)
{
DL = DL->successor; // 忽略头结点
while (DL != NULL)
{
if (DL->data == data)
{
return true;
}
DL = DL->successor;
}
return false;
}
void Print(DLNode *DL)
{
DL = DL->successor; // 忽略头结点
while (DL != NULL)
{
printf("%2d\t", DL->data);
DL = DL->successor;
}
printf("\n");
}
int main()
{
DLNode *DL;
Init(DL);
// HeadInsertionCteate(DL);
TailInsertionCreate(DL);
Print(DL);
/*查找*/
DLNode *temp = GetByPosition(DL, 2);
if (temp != NULL)
{
printf("get position 2 success data is %d\n", temp->data);
}
else
{
printf("get error\n");
}
/*查找2*/
if (IsExist(DL, 888))
{
printf("data = 888 is exist\n");
Print(DL);
}
else
{
printf("not exist\n");
}
/*插入*/
if (InsertByPosition(DL, 2, 999))
{
printf("insert into position 2 data 999 success\n");
Print(DL);
}
else
{
printf("insert fault\n");
}
/*删除*/
if (DeleteByPosition(DL, 3))
{
printf("delete from position 3 success\n");
Print(DL);
}
else
{
printf("delete fault\n");
}
/*修改*/
if (UpdateByPosition(DL, 3, 888))
{
printf("update data = 888 on position 3 success\n");
Print(DL);
}
else
{
printf("update fault\n");
}
}