大纲
双向链表(增删改查)
双向循环链表(增删)
一、双向链表
双向链表的结点有连个指针域,一个(prior)指向钱一个结点,一个(next)指向后一个结点。
#include <stdio.h>
#include "string.h"
#include "ctype.h"
#include "stdlib.h"
#include "math.h"
#include "time.h"
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OK 1
#define MAXSIZE 20 //存储空间初始分配量
typedef int Status;//Status 是函数的类型,其值是函数结果状态代码l,如OK等
typedef int ElemType;//ElemType 类型根据实际情况而定,这里假设为int
typedef struct Node{
struct Node * prior;
ElemType data;
struct Node * next;
}Node;
typedef struct Node * LinkList;
Status createList(LinkList *L){
1、 双向链表创建初始化
*L = (LinkList)malloc(sizeof(Node));
if (*L == NULL) return ERROR;
// 头结点
(*L)->prior = NULL;
(*L)->data = -1;//不会用到设置不设置都行
(*L)->next = NULL;
// 新增结点
LinkList p = *L;
for (int i=10; i<20; i++) {
LinkList temp = (LinkList)malloc(sizeof(Node));
if (temp == NULL) return ERROR;
temp->prior = NULL;
temp->data = i;
temp->next = NULL;
// 建立一个双向链表关系
p->next = temp;
temp->prior = p;
p = p->next;
}
return OK;
}
2、双向链表的遍历
//2、遍历双向链表
void display(LinkList L){
LinkList temp = L->next;
if (temp == NULL) {
printf("双向链表为空\n");
return;
}
while (temp) {
printf("%d ", temp->data);
temp = temp->next;
}
printf("\n");
}
3、双向链表的插入
思路:
1、判断插入位置是否合法(<1)
2、创建一个新结点(temp),并赋值。
3、创建一个指针(p)指向头结点,用于变量链表找到插入位置前一个结点。
4、找到要插入位置的前一个结点,并判断是否超出链表长度(p==NULL)。
5、判断是否在链表尾部(尾部的话只需要处理插入结点的前驱指向,和前一个结点的后驱指向就行)。
6、插入位置不在尾结点。(例如: a b 之间插入c)
(1)、将插入位置后一个结点的前驱指向要插入的结点。 b->prior = c;
(2)、将要插入结点的后驱指向插入位置后一个结点。 c->next = b;
(3)、将插入前一个结点的后驱指向要插入的结点。a->next = c;
(4)、将要插入的结点的前驱指向插入位置前一个结点。c->prior = a;
(1)(2)顺序可换,(3)(4)顺序可换。但是整体的(1)(2) 和 (3)(4)的顺序不能换,因为换了之后就找不到后面的结点了,所以需要先指向后面的结点之后再处理前面结点。
Status ListInsert(LinkList *L,int i, ElemType data){
//1、判断位置是否合法
// 有头结点所以如果插入位置小于0不合法,等于0就是插到头结点之前也是不合法位置
if (i<1) return ERROR;
// 2、创建结点
LinkList temp = malloc(sizeof(Node));
temp->prior = NULL;
temp->data = data;
temp->next = NULL;
// 3、将p指向指向 *L 头结点
LinkList p = *L;
// 4、找到插入位置i 前一个结点
for (int j=0; j<i; j++) {
p = p->next;
}
// 5.如果插入的位置超过了链表本身的长度返回错误
if (p == NULL) return ERROR;
// 6、判断插入的位置是否是链表尾部
if (p->next == NULL) {
p->next = temp;
temp->prior = p;
}else{
//(1)、将p->next(插入位置后一个结点)的前驱 prior = temp;
p->next->prior = temp;
//(2)、将temp->next 指向原来的p->next (插入位置后一个结点);
temp->next = p->next;
//(3)、将p-next 更新成新创建的需要插入的 temp;
p->next = temp;
temp->prior = p;
}
return OK;
}
4.1、删除双向链表指定位置上的结点
1、找到删除位置前一个结点
2、用一个指针指向删除结点
3、将删除位置的前后结点进行关联。
4、释放掉要删除的结点。
//指针e 将删除的值带回去。
Status ListDelete(LinkList *L,int i,ElemType *e){
if (i<1) return ERROR;
LinkList p = (*L);//指向头结点遍历找到删除位置前一个结点用
// 1、判断双向链表是否为空,如果为空返回 错误
if (*L == NULL) return ERROR;
// 2、将指针p 指向删除位置前面的结点
for (int j=1; j<i; j++) {
p = p->next;
// 3、如果 p == NULL 返回错误
if (p == NULL) return ERROR;
}
// 4、创建临时指针 delTemp 指向被删除的结点,并将要删除的结点的data 赋值给 *e 带回
LinkList delTemp = p->next;
*e = delTemp->data;
// 5、p->next 等于要删除的结点的下一个结点
p->next = delTemp->next;
// 6、如果删除的结点的下一个结点不为空,则将要删除的结点的下一个结点的前驱指向要删除的上一个结点
if (delTemp->next != NULL) {
delTemp->next->prior = p;
}
// 7、删除delTemp 结点
free(delTemp);
return OK;
}
4.2、双向链表删除指定内容
Status ListDeleteValue(LinkList *L, int data){
LinkList p = (*L)->next;//不从头结点开始,因为头结点data我们也赋的有值,目的不是删除头结点
// 1、遍历双向链表
while (p) {
// 2、判断当前结点的数据域和data是否相等,若相等则删除该结点
if (p->data == data) {
// 修改被删除结点的前驱结点的后继指针
p->prior->next = p->next;
// 修改删除结点的后继结点的前驱指针
if (p->next != NULL) {
p->next->prior = p->prior;
}
// 释放被删除的结点
free(p);
// 退出循环
break;
}
// 不是要找的结点继续往后找
p = p->next;
}
if (p == NULL) return ERROR;
return OK;
}
5、双向链表查找
int selectElem(LinkList L, ElemType elem){
LinkList p = L->next;
int i =1;
while (p) {
if (p->data == elem) {
return i;
}
i++;
p = p->next;
}
return -1;
}
6、双向链表中更新结点
Status replaceLinkList(LinkList *L, int index, ElemType newElem){
if (index <1)return ERROR;
LinkList p = *L;
for (int i=1; i<index; i++) {
p = p->next;
if (p == NULL) return ERROR;
}
p->next->data = newElem;
return OK;
}
二、双向循环链表
双向循环链表,与双向链表的区别就是:双向循环链表的尾结点的后驱指向头结点(这里为了方便处理加了头结点)。
双向循环链表的结点和双向链表的结点一样。
1、创建初始化
Status createList(LinkList *L)
{
*L = (LinkList)malloc(sizeof(Node));
if (*L == NULL) return ERROR;
(*L)->prior = (*L);
(*L)->next = (*L);
// (*L)->data = -1;//这个地方给-1的目的是就是想让其为空,可以不写。
// 新增数据
LinkList p = *L;
for (int i=0; i<10; i++) {
// 1 temp 创建一个临时的结点
LinkList temp = (LinkList)malloc(sizeof(Node));
if (temp == NULL) return ERROR;
temp->data = i;
// 2、为新增的结点建立双向链表关系
// (1)、temp 是p的后继
p->next = temp;
// (2)、temp 的前驱是p
temp->prior = p;
// (3)、temp的后继是 *L
temp->next = (*L);
// (4)、p 的前驱是新建的 temp
p->prior = temp;
// (5)、 p 要记录最后的结点的位置,方便下次插入
p = p->next;
}
return OK;
}
2、遍历
void display(LinkList L){
LinkList temp = L->next;
if (temp == NULL) {
printf("打印的双向链表为空!\n");
}
printf("双向链表的内容:\n");
while (temp != L) {
printf("%d ",temp->data);
temp = temp->next;
}
printf("\n");
}
3、插入元素
//0是头结点,不是位置,位置从1开始
Status ListInsert(LinkList *L, int i, ElemType data){
// if (i<1) return ERROR;
// 1、创建指针p 指向双向链表头
LinkList p = (*L);
// 2、双向链表为空,返回error
if (*L == NULL) return ERROR;
// 3、找到插入位置的前一个 结点
for (int j=1; j<i && p; j++) {
p = p->next;
}
// 4、越界判断,后面的是null
if (p==NULL) return ERROR;
// 5、创建新结点 temp
LinkList temp = (LinkList)malloc(sizeof(Node));
if (temp == NULL) return ERROR;
temp->data = data;
// 6、将结点temp 的前驱结点指向 p
temp->prior = p;
// 7、将temp 的后续结点指向 p->next
temp->next = p->next;
// 8、p 的后续结点为新结点 temp
p->next = temp;
// 9、如果temp 结点不是最后一个结点
if (*L != temp->next) {
//temp 结点的下一个结点的前驱指向 temp
p->next->prior = temp;
}else{
(*L)->prior = temp;
}
return OK;
}
4、删除元素
Status ListDelete(LinkList *L, int i,ElemType *e){
int k =1;
// 1、
if (*L == NULL) return ERROR;
LinkList p = (*L)->next;
// 2、如果删除的只剩下首元结点了,则直接将 *L置空
if (p->next == (*L)) {
free(*L);
(*L) = NULL;
return OK;
}
// 3、找到要删除的结点
while (k<i) {
p = p->next;
k++;
}
// 4、给 e 赋值要删除结点的数据域
*e = p->data;
// 5、修改被删除结点的前一个结点的后继指向
p->prior->next = p->next;
// 6、修改被删除结点的后一个指针的前驱指向
p->next->prior = p->prior;
// 删除结点
free(p);
return OK;
}