双向链表的创建——初始化、查找、插入、删除、删除重复元素
▍抽象数据结构的定义
ADT List{
数据对象:D={ai|ai∈ElemType,i=1,2…,n,n≥0}
数据关系:R1={<a(i-1),ai>| a(i-1),ai∈D,i=1,2…,n }
基本操作:
ListInit(&L)
操作结果:构造一个空的链表,分配内存空间。
First_Init(len)
操作结果:头结点的创建及链表的初始化。
ListInsert(&L,i,e0)
初始条件:线性表已存在。
操作结果:在顺序表L中第i个位置之前插入新的元素e。
ListDelete(&L,I,e)
初始条件:线性表已存在且非空。
操作结果:删除顺序表L中第i个位置的元素并返回,L的长度减1。
ListLocate(L,e0)
初始条件:线性表已存在。
操作结果:(按值查找)查找顺序表中第一个出现的该元素并定位,返回该值所在的位置序号。
ListUnique(L)
初始条件:线性表已存在。
操作结果:将删除链表中所有重复元素,使每个元素在链表中只出现一次,如果没有重复元素,则正常运行。
}ADT List
▍代码
#pragma once
//浮点型双向列表的创建LIST.h
#define OK 0
#define ERROR -1
typedef float Etype; //定义数据元素的类型为浮点型
typedef int Status; //定义函数的返回状态
typedef int Locate; //定义元素所在位置序号
typedef struct DulNode { //结点
Etype data; //数据域
struct DulNode* prior; //前驱指针域
struct DulNode* next; //后继指针域
}DulNode, * DL;
DL First_Init(int len); //链表的初始化(头插法)
Locate ListLocate(DL L, Etype e); //元素查找
Status ListInsert(DL L, Locate i, Etype e); //元素插入
Status ListDelete(DL L, Locate i, Etype& e); //元素删除
void ListUnique(DL L); //重复元素的删除
void FreeList(DL L); //释放内存空间
void Print(DL Dum); //输出函数
//DulNode.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<stdlib.h>
#include"LIST.h"
using namespace std;
int len; //链表的长度
//链表的初始化(尾插法)——正序插入
DL First_Init(int len) {
DL head = (DL)malloc(sizeof(DulNode)); //给头结点分配内存空间
DL m; //m为遍历使用的结点
Etype e; //e为需要输入的数据元素
if (!head) exit(ERROR); //如果分配失败,则退出程序
head->data = 0; //给头结点赋初值
head->prior = NULL; //头结点的前驱指针初始化
head->next = NULL; //头结点的后继指针为NULL
m = head;
for (int i = 0; i < len; i++) {
DL node = (DL)malloc(sizeof(DulNode)); //node为创建使用的结点,每创建一个结点,就给该结点分配对应的内存空间
scanf("%f", &e);
node->data = e; //把输入的值存入结点中的数据元素
node->prior = m; //先右后左
m->next = node; //前一个结点的next指针指向下一个结点
m = node; //传递指针指向
}
m->next = NULL;
return head;
}
//元素查找,按值查找,返回结点数据元素所在位置序号
Locate ListLocate(DL L, Etype e) {
Locate i = 0;
if ((L->next)->data == e) {
return 1;
}
while (L != NULL) {
++i;
if (L->data == e)
return i - 1;
else
L = L->next;
}
printf("[warning]查找元素不存在!");
return 0;
}
//元素按序号插入,在以L为头结点的链表的第l个位置插入值为e的结点
Status ListInsert(DL L,Locate i, Etype e) {
DL n, p = L; //p为遍历使用结点
Locate j = 0;//j存储遍历结点p的序号
while (p && j < i - 1) {//找到要插入位置结点的前驱结点,即p指向第i-1个结点
p = p->next;
j++;
}
if (!p || j > i - 1) {//未查找到该位置
printf("[warning]locate out of range!");
return ERROR;
}
n = (DL)malloc(sizeof(DulNode)); //给要插入的结点分配空间
n->data = e; //给结点数据域赋值
n->next = p->next; //结点的后继指针与i-1结点的后继指针相同
n->prior = p; //结点的前驱指针即i-1结点
p->next = n; //i-1结点的后继指针指向插入结点
(p->next)->prior = n; //i+1结点的前驱指针指向插入结点
len += 1;
return OK;
}
//元素删除,按位置序号删除并返回删除的数据元素
Status ListDelete(DL L, Locate i, Etype& e) {
if (i > 0 && i < len + 1) {
DL p = L; //p为遍历使用结点
Locate j = 0;//j存储遍历结点p的序号
while (p && j < i - 1) {//找到要删除位置结点的前驱结点,即p指向第L-1个结点
p = p->next;
j++;
}
e = p->next->data; //返回要删除的数据元素
//此为已知前驱结点,如何删除后一个结点的方法
p->next->next->prior = p;
p->next = p->next->next;
len -= 1; //链表长度改变
return OK;
}
else { //输入位置序号不合法(合法范围:1≤i≤len)
printf("[warning]locate out of range!");
//同样的 , 加入删除的数据元素 == 1 那么 无法区分是 ERROR 还是 正常删除元素 1
exit(ERROR);
}
}
//重复元素的删除操作
void ListUnique(DL L) {
//空链表 、 只含一个结点的链表
if (L->next == nullptr || L->next->next == nullptr) return;
//去重
DL p = L->next;
while (p) {
DL pre = p;
while (pre->next) {
if (pre->next->data == p->data)
pre->next = pre->next->next;
else
pre = pre->next;
}
p = p->next;
}
}
//释放L指向结点的空间
void FreeList(DL L) {
for (; L == NULL;) {
free((*L).next);
free(L->prior);
L = L->next;
}
printf("内存空间已释放,进程已结束!");
}
//输出链表
void Print(DL Dum) {
printf("浮点型双向链表可展示为:");
Dum = Dum->next;
for (; Dum != NULL; ) {
printf("%6.4f ", Dum->data);
Dum = Dum->next;
}
printf("\n\n");
}
//界面优化函数
void interface() {
printf(" “查找”请输入1\n “插入”请输入2\n “删除”请输入3\n “删除重复元素”请输入4\n “退出”请输入5\n");
}
int main() {
DL head;
Locate loc; //元素所在位置序号
Etype e; //元素值——存放插入元素、查找元素、删除元素
string c; //操作选择:1-查找 2-插入 3-删除 4-删除所有重复元素 5-退出
//界面优化
printf("——Welcome to our linear list——\n");
printf("【已初始化】正在创建中...\n请输入创建的数据元素个数len=");
scanf("%d", &len);
printf("请输入数据元素(使用空格隔开即可):");
head = First_Init(len);
Print(head); //输出初始化好的顺序表
interface(); //界面优化函数
printf("--please enter your choice:");
cin >> c;
do {
//错误输入提示,可重新输入
if (c != "1" && c != "2" && c != "3" && c != "4" && c != "5") {
printf("[warning]WRONG input,please enter again:");
cin >> c;
}
//查找元素的操作(按值查找)
if (c == "1") {
printf("【该操作查找的均是第一次出现的元素位置!】\n");
printf("请输入要查找的元素值 e=");
scanf("%f", &e);
loc = ListLocate(head, e);
if (loc) {
printf("查找元素的位置在第%d位", loc);
}
else {
printf("错误操作!");
}
}
//插入元素的操作(按序号插入)
else if (c == "2") {
printf("请输入想要插入元素所在的序号(1≤i≤%d):i=", len);
scanf("%d", &loc);
if (loc >= 1 && loc <= len) {
printf("请输入想要插入的数据元素值(请输入浮点数):e=");
scanf("%f", &e);
ListInsert(head, loc, e);
printf("——插入完成!\n");
Print(head);
}
else {
printf("[warning]Insert out of range!");
}
}
//删除元素操作
else if (c == "3") {
printf("请输入要删除的元素所在序号 i=");
scanf("%d", &loc);
ListDelete(head, loc, e);
if (e == ERROR) {
puts("[warning]Delete out of range!");
}
else {
printf("——删除完成!删除的元素是:%6.4lf\n", e);
Print(head);
}
}
//删除重复元素操作
else if (c == "4") {
printf("【该操作将删除链表中所有重复元素,使每个元素在链表中只出现一次,如果没有重复元素,则正常运行】\n");
ListUnique(head);
printf("——删除成功!\n");
Print(head);
}
else if (c == "5")break;
printf("\n———--please enter your choice again:");
cin >> c;
} while (c == "1" || c == "2" || c == "3" || c == "4");
FreeList(head);
return 0;
}
▍时间复杂度与空间复杂度分析
(链表长度为n)
- 元素的查找(ListLocate)
在按值查找的函数中,算法的时间耗费在指针移动(链表的遍历)上
则算法的时间复杂度为O(n);
在查找时,内存空间并没有发生变化,所以算法的空间复杂读为O(1)。
- 元素的插入(ListInsert)
如果我们想从双链表中删除一个现有的结点 cur,可以简单地将它的前一个结点 prior 与下一个结点 next 链接起来。
因为需要遍历链表来获取前一个结点,则算法的时间复杂度为O(n);又因为每次插入都动态申请内存空间,所以算法的空间复杂读为O(1)。
- 元素的删除(ListDelete)
与插入操作相同,需要对指针进行遍历
则算法的时间复杂度为O(n);又因为每次删除不涉及动态申请内存空间变化,所以算法的空间复杂读为O(1)。
▍实验事例验证与分析
①链表的初始化:输入数据元素个数和对应的数据元素
②查找操作
③ 插入操作
④删除操作
⑤删除重复元素操作
⑥对于选择输入错误提示:
⑦对于查找元素不存在时:
⑧对于插入元素位置越界时:
⑨对于删除位置越界时:(会自动退出进程)
⑩对于链表中没有重复元素时:(会按照原链表输出,但会给出删除提示,此处可忽略(有待改进))