系列文章目录
第一章:线性表的定义和基本操作
第二章:双链表
线性表的定义和基本操作
一、线性表的顺序表示
线性表的顺序存储又称顺序表。它是用一组地址连续的存储单元一次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理位置上也相邻。第1个元素存储在线性表的起始位置,第i个位置存储位置紧接着存储的是第i+1个元素,称I为元素ai在线性表中的位序。
1.数组描述顺序存储结构
首先是定义结构体,对数组进行静态分配,当然也可以进行动态分配。
静态分配的话,数组的大小和空间是已经固定了的,一旦空间占满,再加入新的数据就会溢出使得程序崩溃。而动态分配的话就不这样,动态分配中存储数组的空间是在程序执行过程中通过动态存储分配语句来分配的,一旦空间占满的话,就会开辟新的更大的空间来替换掉以前的空间。
C语言的初始动态分配的语句:
L.data=(ElemType* )malloc(sizeof(ElemType)* InitSize);
C++的初始动态分配的语句:
L.data=new ElemType(InitSize);
注意:线性表中的元素的位序是从1开始,而数组中的元素下标是从0开始。
代码如下(静态分配):
#define MaxSize 50 //定义线性表的最大长度
typedef int ElemType;
//静态分配
typedef struct {
ElemType data[MaxSize]; //顺序表的元素
int length; //顺序表的长度
}SqList; //顺序表的类型
2.顺序表的基本操作
(1)插入操作
在顺序表L中第i(1<i<length+1)个位置插入一个新元素e,如果i的的输入不合法,则显示插入失败返回FALSE,反之,则腾出一个新的位置插入新元素e,并将第i个元素及其后面的元素向后移一个位置,顺序表的长度+1。
代码如下:
//插入元素
bool ListInsert(SqList& L, int i, ElemType e)
{
if (i<1 || i>L.length + 1) //判断i的范围是否有效
return false;
if (L.length >= MaxSize) //大于存储空间的话则插入失败
return false;
for (int j = L.length; j >= i; j--) // 将第i个元素及之后的元素向后移
L.data[j] = L.data[j - 1];
L.data[i - 1] = e; //在位置i处放入e
L.length++; //线性表长度加1
return true;
}
若在表头插入元素,则i=1,后面元素将执行n次后移,时间复杂度为O(n)。
若在表尾插入元素,则i=n+1,整体元素不需要移动,时间复杂度为O(1)。
若在表中插入元素,则需要将第i个元素及其后面的元素向后移一个位置,平均时间负责度是O(n)。
(2)删除操作
删除顺序表L中的第i(1<i<length+1)个元素,使用引用变量e返回,若i的输入不合法,则删除失败返回FALSE,反之,将要删除的元素赋值给e,并且将删除的第i个元素及其后面的元素向前移动一个位置,顺序表的长度-1。
代码如下:
//删除元素
bool ListDelete(SqList& L, int i, ElemType& e) { //使用c++的引用(使用&)是因为要改变里面的值,即删除,比如查询跟插入就不需要
if (i<1 || i>L.length + 1)
return false;
if (L.length == 0)
return false;
e = L.data[i - 1]; //将被删除的元素赋值给e
for (int j = i; j < L.length; j++) //将第i个元素后面的元素向前移
L.data[j - 1] = L.data[j];
L.length--; //线性表长度减1
return true;
}
若删除表头,则i=1,需要移动除表头外的所以元素,时间复杂的为O(n)。
若删除表尾,则i=n,不需要移动,时间复杂度为O(1)。
若删除其中一个元素,则需要将删除的第i个元素及其后面的元素向前移动一个位置,平均时间复杂度为O(n)。
(3)查找操作
查找顺序表中的第i个元素,引用变量e,循环数组,查询与e匹配的值并返回。
代码如下:
//查找元素
int LocateElem(SqList L, ElemType e)
{
int i;
for (i = 0; i < L.length; i++)
if (L.data[i] == e)
return i + 1; //+1代表元素在顺序表中的位置
return 0;
}
若查找的元素在表头,则只需要比较一次,时间复杂度为O(1)。
若查找的元素在表尾(或者不存在)时,则需要比较n次,时间复杂度为O(n)。
若查找的元素在中间,平均时间复杂度为O(n)。
二、线性表的链式表示
1.单链表的定义
线性表的单链表又称单链表,它是通过一组任意的存储单元来存储线性表中的数据元素。为了建议数据元素之间的线性关系,对每个链表结点,除了存放自身信息外,还需要存放一个指向其后继的指针。
通常,为了操作的方便,会在单链表第一个结点之前附加一个结点,称为头结点。头结点的数据域可以是空。头结点的指针域指向线性表的第一个元素的结点。
单链表中结点类型的描述:
代码如下:
typedef int ElemType;
typedef struct LNode { //定义单链表结点类型
ElemType data; //数据域
struct LNode* next; //指针域
}LNode,*LinkList;
2.单链表的基本操作
(1)头插法建立单链表
该方法从一个空表开始,生成新结点,并将读取到的数据存放在新结点的数据域中,然后将新结点插入到当前链表的表头,即头结点之后。但是插入的顺序在显示的时候是逆序显示。
代码如下:
//头插法
LinkList CreatList1(LinkList& L) {
LNode* s; int x;
L = (LinkList)malloc(sizeof(LNode)); //创建头结点
L->next = NULL; //初始为空链表
scanf_s("%d", &x); //输入结点的值
while (x != 999) //再输入999之后表示结束
{
s = (LinkList)malloc(sizeof(LNode)); //创建新的结点s,给其申请空间,并强制类型转换
s->data = x; //将读取到的值给新空间s中的data成员
s->next = L->next; //让新结点的next指针指向链表的第一个元素
L->next = s; //让s作为第一个元素,值为x
scanf_s("%d", &x);
}
return L;
}
(2)尾插法建立单链表
尾插法跟头插法的区别就是显示插入数据的次序不一样,尾插法是按插入顺序显示。
增加一个尾指针r,使其始终指向当前链表的尾结点。
代码如下:
//尾插入法
LinkList CreatList2(LinkList& L) {
int x;
L = (LinkList)malloc(sizeof(LNode));
LNode* s, * r = L; //r为表尾的指针
scanf_s("%d", &x);
while (x!=999)
{
s = (LinkList)malloc(sizeof(LNode));
s->data = x;
r->next = s; //让尾部结点指向新的结点
r = s; //r指向新的表尾结点
scanf_s("%d", &x);
}
r->next = NULL; //尾结点指针置空
return L;
}
(3)按序号查找结点值
在单链表中从第一个结点出发,顺时针next域逐个往下搜索,直到找到第i个结点为止,否则返回最后一个结点指针域null。
代码如下:
//按序号查找结点值
LinkList GetElem(LinkList L, int i)
{
int j = 1; //计数,初始为1
LinkList p = L->next; //第1个结点指针赋给p
if (i == 0)
return L;
if (i < 1)
return NULL;
while (p && j < i) //从第1个结点开始找,查找第i个结点
{
p = p->next;
j++;
}
return p;
}
(4)按值查找表结点
引用变量e,并将目标值赋值给e,在单链表中从第一个结点开始比较各结点数据域的值,找到匹配值则返回结点指针,否则返回最后一个结点的指针域 NULL。
代码如下:
//按值查找表结点
LinkList LocateElem(LinkList L, ElemType e)
{
LinkList p = L->next;
while (p != NULL && p->data!=e) //从第1个结点开始查找data域为e的结点
{
p=p->next;
return p;
}
}
(5)插入结点
插入结点操作即将值为x的新结点插入到单链表中的第i个位置,显示检查插入的位置是否合法,然后找到插入元素的前驱结点,即第i-1个结点,再在其后插入新结点。
算法首先调用按序号查找算法GetElem(L,i-1),查找第i-1个结点。假设返回的第i-1个结点为* p,然后令新结点* s的指针域指向* p的后继结点,再令结点* p的指针域指向新插入的新结点* s。
图中的第1步跟第2步顺序不能颠倒,否则,指执行p->next = s后,指向其原后继的指针就不存在了,再执行s->next = p->next,相当于执行了s->next =s,显然是不行的。
代码如下:
//在第i个位置插入元素
bool ListFrontInster(LinkList L, int i, ElemType e)
{
LinkList p = GetElem(L, i - 1); //查找插入元素的前驱结点
if (p == NULL)
{
return false;
}
LinkList s = (LinkList)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
(6)删除结点
将单链表中的第i个结点删除,先检查删除的位置是否合法,后查找表中第i-1个结点,即被删除结点的前驱结点,再将其删除。
假设结点* p为找到的被删除结点的前驱结点,再将* p的指针域next指向*q的下一结点。
代码如下:
//删除元素
bool ListDelete(LinkList L, int i)
{
LinkList p = GetElem(L, i - 1);
if (p == NULL)
{
return false;
}
LinkList q = p->next; //令q指向被删除结点
p->next = q->next; //将*q结点从链中“断开”
free(q); //释放结点的存储空间
q = NULL; //避免野指针
return true;
}
三、顺序表和链式表的增删查代码汇总
1.顺序表
注:以上分布讲解的代码均为伪代码,下面的代码是总的汇总,可直接复制并运行。
代码如下:
#include<stdio.h>
#define MaxSize 50 //定义线性表的最大长度
typedef int ElemType;
//静态分配
typedef struct {
ElemType data[MaxSize]; //顺序表的元素
int length; //顺序表的长度
}SqList; //顺序表的类型
//打印线性表
void PrintList(SqList& L)
{
for (int i = 0; i < L.length; i++)
{
printf("%4d", L.data[i]);
}
printf("\n");
}
//插入元素
bool ListInsert(SqList& L, int i, ElemType e)
{
if (i<1 || i>L.length + 1) //判断i的范围是否有效
return false;
if (L.length >= MaxSize) //大于存储空间的话则插入失败
return false;
for (int j = L.length; j >= i; j--) // 将第i个元素及之后的元素向后移
L.data[j] = L.data[j - 1];
L.data[i - 1] = e; //在位置i处放入e
L.length++; //线性表长度加1
return true;
}
//删除元素
bool ListDelete(SqList& L, int i, ElemType& e) { //使用c++的引用(使用&)是因为要改变里面的值,即删除,比如查询跟插入就不需要
if (i<1 || i>L.length + 1)
return false;
if (L.length == 0)
return false;
e = L.data[i - 1]; //将被删除的元素赋值给e
for (int j = i; j < L.length; j++) //将第i个元素后面的元素向前移
L.data[j - 1] = L.data[j];
L.length--; //线性表长度减1
return true;
}
//查找元素
int LocateElem(SqList L, ElemType e)
{
int i;
for (i = 0; i < L.length; i++)
if (L.data[i] == e)
return i + 1; //+1代表元素在顺序表中的位置
return 0;
}
int main()
{
SqList L;
bool ret; //查看返回值,bool型是true,或者是false
L.data[0] = 1;
L.data[1] = 2;
L.data[2] = 3;
L.length = 3;
ret = ListInsert(L, 2, 60); //在L表中的第2个位置插入60
if (ret)
{
printf("插入成功\n");
PrintList(L);
}
else {
printf("插入失败\n");
}
ElemType del; //删除
del = ListDelete(L, 1, del);
if (del)
{
printf("删除成功\n");
PrintList(L);
printf("删除成功的值是:%d\n", del);
}
else {
printf("删除失败\n");
}
int loc; //查找
loc = LocateElem(L, 60);
if (loc)
{
printf("查找成功\n");
printf("元素位置为%d\n",ret);
}
else
{
printf("查找失败\n");
}
return 0;
}
2.单链表
代码如下:
#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
typedef struct LNode { //定义单链表结点类型
ElemType data; //数据域
struct LNode* next; //指针域
}LNode,*LinkList;
//链表打印
void PrintList(LinkList L) {
L = L->next;
while (L!=NULL)
{
printf("%4d", L->data);
L = L->next;
}
printf("\n");
}
//头插法
LinkList CreatList1(LinkList& L) {
LNode* s; int x;
L = (LinkList)malloc(sizeof(LNode)); //创建头结点
L->next = NULL; //初始为空链表
scanf_s("%d", &x); //输入结点的值
while (x != 999) //再输入999之后表示结束
{
s = (LinkList)malloc(sizeof(LNode)); //创建新的结点s,给其申请空间,并强制类型转换
s->data = x; //将读取到的值给新空间s中的data成员
s->next = L->next; //让新结点的next指针指向链表的第一个元素
L->next = s; //让s作为第一个元素,值为x
scanf_s("%d", &x);
}
return L;
}
//尾插入法
LinkList CreatList2(LinkList& L) {
int x;
L = (LinkList)malloc(sizeof(LNode));
LNode* s, * r = L; //r为表尾的指针
scanf_s("%d", &x);
while (x!=999)
{
s = (LinkList)malloc(sizeof(LNode));
s->data = x;
r->next = s; //让尾部结点指向新的结点
r = s; //r指向新的表尾结点
scanf_s("%d", &x);
}
r->next = NULL; //尾结点指针置空
return L;
}
//按序号查找结点值
LinkList GetElem(LinkList L, int i)
{
int j = 1; //计数,初始为1
LinkList p = L->next; //第1个结点指针赋给p
if (i == 0)
return L;
if (i < 1)
return NULL;
while (p && j < i) //从第1个结点开始找,查找第i个结点
{
p = p->next;
j++;
}
return p;
}
//按值查找表结点
LinkList LocateElem(LinkList L, ElemType e)
{
LinkList p = L->next;
while (p != NULL && p->data!=e) //从第1个结点开始查找data域为e的结点
{
p=p->next;
return p;
}
}
//在第i个位置插入元素
bool ListFrontInster(LinkList L, int i, ElemType e)
{
LinkList p = GetElem(L, i - 1); //查找插入元素的前驱结点
if (p == NULL)
{
return false;
}
LinkList s = (LinkList)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
//删除元素
bool ListDelete(LinkList L, int i)
{
LinkList p = GetElem(L, i - 1);
if (p == NULL)
{
return false;
}
LinkList q = p->next; //令q指向被删除结点
p->next = q->next; //将*q结点从链中“断开”
free(q); //释放结点的存储空间
q = NULL; //避免野指针
return true;
}
int main()
{
LinkList L;
LinkList search;
CreatList1(L);
PrintList(L);
CreatList2(L);
PrintList(L);
search = GetElem(L, 2);
if (L != NULL)
{
printf("按序号查找成功\n");
printf("%4d\n", search->data);
}
search = LocateElem(L, 3);
if (search != NULL)
{
printf("按值查找成功\n");
printf("%4d\n", search->data);
}
ListFrontInster(L, 2, 99);
PrintList(L);
ListDelete(L, 4);
PrintList(L);
return 0;
}
总结
这是复习数据结构中第二章线性表的前两个知识点的总结,顺序表的话相对来说比较简单且使用的并不是很多,单链表的话就比较常见,后面还有双链表之类的,如果有什么不错误的地方还希望大家能够指点指点。希望自己的总结能够对大家有所帮助,如果对你能有所帮助就点赞收藏支持我呗!