双向链表的几种插入,删除,指定位置输出
双向链表的长相
双向链表由前驱指针和后项指针还有数据域组成,把节点之间的前后指针相连接形成链表
双向链表节点的封装
双向链表与单链表相比,仅多了一个前驱指针
typedef struct Node {
int data;//内容
struct Node* frontNode;//前驱指针
struct Node* nextNode;//后项指针
}*LPNODE;
双向链表的特点在于可以从左到右,也可以从右到左输出链表,因此需要一个头节点和尾节点分别指向链表的头和尾,实现链表的顺序或逆序打印
封装链表
typedef struct List {
struct Node* HeadNode;//头节点
struct Node* TailNode;//尾节点
int curSize;//万金油参数
}*LPLIST;
万金油参数负责记录节点个数,方便后续条件判断
创建头节点和尾节点
这里主要是起到了封装头节点和尾节点的作用,函数初始化节点,返回相应节点
LPNODE CreateHeadNode() {
LPNODE headNode = (LPNODE)malloc(sizeof(Node));//动态申请一个节点大小的内存
assert(headNode);//判空
headNode->frontNode = NULL;
headNode->nextNode = NULL;
return headNode;
}
LPNODE CreateTailNode() {
LPNODE tailNode = (LPNODE)malloc(sizeof(Node));
assert(tailNode);
tailNode->frontNode = NULL;
tailNode->nextNode = NULL;
return tailNode;
}
判空是因为内存可能会申请失败,返回NULL。该函数是为了避免因赋值到空指针而影响到后续指针的操作。
创建新节点
LPNODE CreateNode(int data) {
LPNODE newNode = (LPNODE)malloc(sizeof(Node));
assert(newNode);
newNode->data = data;//和头节点,尾节点不同,所要插入的节点是要初始化data的
newNode->frontNode = NULL;
newNode->nextNode = NULL;
return newNode;
}
创建的newNode目的是方便后续新节点的创建
创建链表,初始化数据
LPLIST CreateList() {
LPLIST list = (LPLIST)malloc(sizeof(List));//申请一个List大小的内存
assert(list);//判空
list->curSize = 0;//当前节点为0
list->HeadNode = CreateHeadNode();//初始化头节点
list->TailNode =CreateTailNode();//初始化尾节点
return list;
}
头插法
这里的要点就是,当节点为0时,头节点的下一个节点是NULL,如果要按再次插入的话,必须要求头节点的下一个节点的前驱指针指向新节点,这里会因为该节点是NULL而不能人为操作空指针而引发中断
尾节点指向最后的节点,因为头插法是一直往中间插的,尾节点在末尾的位置一直没有发生改变
代码如下:
void insertByHead(LPLIST list, int data) {
LPNODE newNode = CreateNode(data);
if(list->curSize==0)//当没有节点时
list->TailNode = newNode;//为节点指向新节点
else {
newNode->nextNode = list->HeadNode->nextNode;//新节点的后项指针指向头节点的下一个
list->HeadNode->nextNode->frontNode = newNode;//新节点的前驱指针指向新节点
}
list->HeadNode->nextNode = newNode;//头节点的后项指针指向新节点
newNode->frontNode = list->HeadNode;//新节点的前驱指针指向头节点
list->curSize++;//每插入一个,增加一个节点数目
}
尾插法
尾插法的要点和头插法类似,也是因为当节点为0时,后项指针会为空
此外,每插入的新节点就是尾节点,尾节点的指向要改变
代码如下:
void insertByTail(LPLIST list, int data) {
LPNODE newNode = CreateNode(data);
if (list->curSize == 0)//节点为空时
list->HeadNode =newNode;
else {
list->TailNode->nextNode = newNode;//直接插在后面
newNode->frontNode = list->TailNode;
}
list->TailNode = newNode;//改变尾节点
list->curSize++;
}
指定位置插入
思路:准备一个指针在前,一个指针在后,两个指针齐头并进,当后面的指针指向指定位置时,把数据插入到两个指针中间
这里的要点是如果没有找到指定数据,要把数据插入到链表后面,同时尾节点发生改变
代码如下:
void insertByAppoint(LPLIST list, int data, int posData) {
LPNODE LeftNode = list->HeadNode->nextNode;//左指针
LPNODE curNode = list->HeadNode->nextNode;//当前指针
while (curNode != NULL && curNode->data != posData) {//没有找到就一直找,找到CurNode为空
LeftNode = curNode;//移动左指针
curNode = curNode->nextNode;//移动当前指针
}
LPNODE newNode = CreateNode(data);//创建新节点
//后面就是插入中间的操作
LeftNode->nextNode = newNode;
newNode->frontNode = LeftNode;
if (curNode == NULL)//尾节点改变
list->TailNode = newNode;
if (curNode != NULL) {
newNode->nextNode = curNode;
curNode->frontNode = newNode;
}
list->curSize++;//节点个数增加
}
节点删除
这里的思路和上面类似,准备两个指针,一个在前,一个在后,齐头并进。当找到需要删除的节点的位置停止移动,把后面指针的下一个节点和前指针指向的节点连接起来,最后释放后指针所指向节点的内存
代码如下:
void DeleteByAppoint(LPLIST list, int posData) {
LPNODE frontNode = list->HeadNode;//前指针
LPNODE curNode = list->HeadNode;//当前指针
while (curNode != NULL && curNode->data != posData) {
frontNode = curNode;
curNode = curNode->nextNode;
}
if (curNode == NULL)//如果找不到就返回
return;
//连接curNode前后位置的节点
frontNode->nextNode = curNode->nextNode;
curNode->nextNode->frontNode = frontNode;
free(curNode);//释放内存
curNode = NULL;
list->curSize--;//删除一个,节点少一个
}
链表的有序插入
准备一堆乱序的数据,通过有序插入,可以返还有序的链表
思路:按顺序一个个插入,当找到下一个节点的数值比所要插入节点中的数值大或者小时,插入该节点前面,形成从小到大或者从大到小的排序,没有找到就插在后面
要点:
1.如果要插入到最后面,就不需要把后项指针的前驱指针指向前面的节点,因为误操作空指针会引发程序中断
2.新节点当插入到最后的位置时,尾节点要发生改变
代码如下:
void insertBySqList(LPLIST list, int Mydata) {
LPNODE frontNode = list->HeadNode;//前指针
LPNODE PosNode = list->HeadNode->nextNode;//指定位置指针
while (PosNode != NULL && PosNode->data < Mydata) {
frontNode = PosNode;//移动
PosNode = PosNode->nextNode;//移动
}
LPNODE newNode = CreateNode(Mydata);//新节点创建
frontNode->nextNode = newNode;
newNode->frontNode = frontNode;
if (PosNode == NULL)//改变尾节点
list->TailNode = newNode;
if (PosNode != NULL) {
PosNode->frontNode = newNode;
newNode->nextNode = PosNode;
}
list->curSize++;
}
打印链表
可以从头到尾打印,也可以从尾到头打印
1.从头到尾
void printListByHead(LPLIST list) {
printf("头输出:\t");
LPNODE pMove = list->HeadNode->nextNode;
while (pMove) {
printf("%d\t", pMove->data);
pMove = pMove->nextNode;
}
printf("\n");
}
2.从尾到头
void printListByTail(LPLIST list) {
printf("尾输出:\t");
LPNODE pMove = list->TailNode;
while (pMove!=list->HeadNode) {
printf("%d\t", pMove->data);
pMove = pMove->frontNode;
}
printf("\n");
}
测试代码:
为了控制台输出数据的美观,我增加了一些换行符和文字修饰
代码如下:
int main() {
LPLIST list = CreateList();
printf("\n尾插法\n");
for (int i = 0; i < 5; i++)
insertByTail(list, i);
printListByHead(list);
printListByTail(list);
printf("删除指定数据的");
DeleteByAppoint(list, 3);
printListByHead(list);
LPLIST list2 = CreateList();
printf("\n头插法\n");
for (int i = 0; i < 5; i++)
insertByHead(list2, i);
printListByHead(list2);
printListByTail(list2);
printf("删除指定数据的");
DeleteByAppoint(list2, 2);
printListByTail(list2);
printf("插入指定数据的");
insertByAppoint(list2, 100, 2);
printListByTail(list2);
printf("插入指定数据的");
printListByHead(list2);
LPLIST list3 = CreateList();
int array[] = { 3,5,9,7,2,6 };
printf("有序链表构建的");
for(int i=0;i<6;i++)
insertBySqList(list3, array[i]);
printListByTail(list3);
printf("有序链表构建的");
printListByHead(list3);
return 0;
}
运行效果如下: