线性表(注意:大题!!!)
线性表的定义和基本操作
线性表的各个数据元素有先后顺序,数据类型相同,所占空间大小相等
第一段代码:&的作用
“带回去”
#include <iostream>
using namespace std;
void test(int x)
{
x = 1024;
cout << "test函数内部 x=" <<x;
cout <<"\n";
}
void test2(int & x) //x为引用型参数
{
x = 1024;
cout << "test函数内部 x=" <<x;
cout <<"\n";
}
int main()
{
int x=1;
cout<<"调用test函数前x="<<x;
cout <<"\n";
test2(x); //调用test2函数后,参数x的值会被函数中的操作修改
cout << "调用test函数后x="<<x; //输出结果为1024
cout <<"\n";
return 0;
}
线性表的基本操作
函数名 | 作用 | 详细描述 |
---|---|---|
InitList(&L) | 初始化表 | 构造一个空的线性表L,分配内存空间 |
DestoryList(&L) | 销毁操作 | 销毁线性表,并释放线性表L所占用的内存空间 |
ListInsert(&L,i,e) | 插入操作 | 在表L中的弟第i个位置上插入指定元素e |
ListDelete(&L,i,&e) | 删除操作 | 删除表L中第i个位置上的元素,并用e返回删除元素的值 |
LocateElem(L,e) | 按值查找 | 在表L中查找具有给定关键字值的元素 |
GetElem(L,i) | 按位查找 | 获取表中第i个位置的元素 |
Empty(L) | 判空操作 | 若L为空表,则返回true,否则返回false |
Length(L) | 求表长 | 返回线性表L的长度,即L中数据元素的个数 |
PrintList(L) | 输出操作 | 按前后顺序输出线性表L 的所有元素值 |
对数据的操作
创建
消毁
增加
删除
查找
修改
为什么要实现对数据结构的基本操作?
便于别人方便使用
避免重复工作
顺序表的主要特点
1、随机访问(因其在物理内存中连续存放)
2、存储密度高,每个节点只存储数据元素
3、扩容不方便
4、插入和删除需要移动大量元素
顺序表的定义
顺序表:用顺序存储方式实现线性表
顺序存储:逻辑上相邻的元素存储在物理位置上也相邻的存储单元中
如何知道数据元素的大小:
C语言:sizeof(ElemType),如sizeof(int)
顺序表的实现——静态分配
数组长度一旦确定就不允许改变
初始化即给数组各个元素分配连续的存储空间,大小为MaxSize*sizeof(Elemtype)
定义数据元素结构体
typedef struct{
ElemType data[MaxSize];//用静态的“数组”存放数据元素
int length;
}SqList;
对数组进行初始化
//基本操作一:初始化一个顺序表
void InitList(SqList &L)
{
for(int i=0;i<MaxSize;i++)
L.data[i]=0;//可省略
L.length=0;//不可省略
}
主函数调用
int main(){
SqList L;
InitList(L);
return 0;
}
静态分配局限性:
顺序表大小一旦确定不可更改
顺序表的实现——动态分配
#define IniteSize 10
typedef struct{
ElemType *data;
int MaxSize;
int length;
}SeqList;
//初始化
void InitList(SeqList &L)
{
L.data=(ElemType *)malloc(InitSize*sizeof(ElemType));
L.length=0;
L.MaxSize=InitSize;
}
//容量扩充
void IncreaseItem(SeqList &L,int len)
{
int *p=L.data;
L.data=(ElemType*)malloc((L.MaxSize+len)*sizeof(ElemType));
for(int i=0;i<L.length;i++)
L.data[i] = p[i];
L.MaxSize = L.MaxSize + len;
free(p);
}
动态分配扩充容量需要的时间长
顺序表的基本操作——插入
若在第i个位置插入某个元素,则第i个元素及其之后的元素都要向后移
bool ListInsert(SqList &L,int i,ElemType e)//i为位序,从1开始
{
if(L.length >= MaxSize)
return false;
if(i<1 || i>L.length+1)
return false;
for(int j=L.length;j>=i;j--)
L.data[j]=L.data[j-1];
L.data[i-1]=e;
L.length++;
return true;
}
时间复杂度分析
最好时间复杂度:O(1),新元素插入到第 n+1个位置,不用挪动元素,即不用执行for循环
最坏时间复杂度:O(n),新元素插入到 第 1 个位置,需移动n个元素,需执行n次for 循环
平均时间复杂度:O(n)
平均时间复杂度计算方法:
i=1;循环n次
i=2;循环n-1次
i=3;循环n-2次
·
·
·
i=n+1;循环0次
平均循环次数:1/(n+1)(0+1+2+···+n)=n/2
顺序表的基本操作——删除
bool ListDelete(SqList &L,int i,ElemType &e)
{
if(i<1 || i>L.length)
return false;
e = L.data[i-1];
for(int j=i;j<L.length;j++)
L.data[j-1]=L.data[j];
L.length = L.length-1;
return true;
}
时间复杂度分析
最好时间复杂度:O(1),删除最后一个元素,不用挪动元素,即不用执行for循环
最坏时间复杂度:O(n),删除 第 1 个元素,需移动n-1个元素,需执行n-1次for 循环
平均时间复杂度:O(n)
平均时间复杂度计算方法:
i=1;循环n-1次
i=2;循环n-2次
i=3;循环n-3次
·
·
·
i=n;循环0次
平均循环次数:1/n(0+1+2+···+n-1)=(n-1)/2
顺序表的基本操作——查找
按位查找
bool GetElem(SqList L,int i,ElemType &e)//动态分配的数组也可直接用该方式按位查找
{
if(i<1 || i>L.length)
return false;
e = L.data[i-1];
return true;
}
时间复杂度:O(1),顺序表随机存取特性,数据表在内存中所有元素顺序存储
按值查找
int LocateElem(SqList L,ElemType e)
{
for(int i=0;i<L.length;i++)
if(L.data[i] == e)
return i+1;//返回位序
return 0;
}
注意:结构体类型的比较要逐项比较结构体各个分量
时间复杂度
最好时间复杂度:O(1),目标元素在表头,循环1次
最坏时间复杂度:O(n),目标元素在表尾,循环n次
平均时间复杂度: O(n)
线性表的链式表示
带头结点的单链表
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode, *LinkList;
//初始化
bool InitList(LinkList &L)
{
L=(LNode *)malloc(sizeof(LNode));
if(L==NULL)
return false;//内存不足,分配失败
L->next=NULL;
return true;
}
//判断没有头结点的单链表是否为空
bool Empty(LinkList L)
{
return (L == NULL);
}
//判断有头结点的单链表是否为空
bool Empty(LinkList L)
{
return (L->next == NULL);
}
单链表基本操作——插入
指定位置的插入操作
带头结点
bool InsertList(LinkList &L,int i,ElemType e)
{
if(i<1)
return false;
LNode *p;//指针p指向当前扫描到的结点
int j=0;//当前p指向哪一个节点
p=L;//指向头结点,无数据
while(j<i-1 && p!=NULL)
{
p = p->next;
j++;
}
if(p == NULL)
return false;
LNode *s=(LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
时间复杂度:最好,O(1);最坏,O(n);平均:O(n)
不带头结点
bool InsertList(LinkList &L,int i,ElemType e)
{
if(i<1)
return false;
if(i==1)//插入第一个元素时需修改头指针的指向
{
LNode *s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=L->next;
L=s;
return true;
}
LNode *p;
int j=1;
p=L;
while(p!=NULL && j<i-1)
{
p=p->next;
j++;
}
if(p == NULL) return false;
LNode *s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
时间复杂度:最好,O(1);最坏,O(n);平均:O(n)
指定节点的插入操作
前插
在p节点之前插入元素e
bool InsertPriorNode(LNode *p,ElemType e)
{
if(p ==NULL) return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
if(s==NULL) return false;
s->next=p->next;
p->next=s;
s->data=p->data;
p->data=e;
return true;
}
时间复杂度:O(1)
在p结点前插入一个结点s
bool InsertPriorNode(LNode *p, LNode *s)
{
if(p==NULL || s==NULL) return false;
s->next=p->next;
p->next=s;
temp = s->data;
s->data=p->data;
p->data=temp;
}
时间复杂度:O(1)
后插
bool InsertNextNode(LNode *p,ElemType e)
{
if(p== NULL) return false;
LNode *s=(LNode *)malloc(sizeof(LNode));
if(s==NULL) return false;
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
时间复杂度:O(1)
单链表的建立
尾插法
定义一个指向尾结点的指针
//带头结点
LinkList List_TailInsert(LinkList &L)
{
int x;//ElemType为整型
L = (LNode *)malloc(sizeof(LNode));
L->next=NULL; //可以有,可以没有
LNode *s,*r=L; //r为表尾指针,L为表头指针,s为指向新插入元素的指针
scanf("%d",&x);
while(x!=1000)
{
s=(LNode *)malloc(sizeof(LNode));
s->data=x;
r->next=s;
r=s;
scanf("%d",&x);
}
r->next=NULL;
return L;
}
//不带头结点
LinkList List_TailInsert(LinkList &L)
{
int x;
L = (LNode *)malloc(sizeof(LNode));
L=NULL;//初始化
LNode *s,*r=L;
scanf("%d",&x);
while(x!=1000)
{
s=(LNode *)malloc(sizeof(LNode));
s->data=x;
if(L==NULL)
{
L=s;
r=L;
}
else{
r->next=s;
r=s;
}
scanf("%d",&x);
}
r->next=NULL;
return L;
}
时间复杂度:O(n)
头插法(链表逆置)
//带头结点
LinkList List_HeadInsert(LinkList &L)
{
int x;
L = (LNode *)malloc(sizeof(LNode));
L->next=NULL; //一定要有!!!!!
LNode *s;
scanf("%d",&x);
while(x!=1000)
{
s=(LNode *)malloc(sizeof(LNode));
s->data=x;
s->next=L->next;
L->next=s;
scanf("%d",&x);
}
return L;
}
//不带头结点
LinkList List_HeadInsert(LinkList &L)
{
int x;
L= (LNode *)malloc(sizeof(LNode));
L=NULL;
LNode *s;
scanf("%d",&x);
while(x!=1000)
{
s=(LNode *)malloc(sizeof(LNode));
s->data=x;
s->next=L;
L=s;
scanf("%d",&x);
}
return L;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ndLk5gs8-1635472455111)(E:\桌面\新建文件夹\头插法.PNG)]
和后插操作类似
实现链表逆置
利用头结点插入实现链表逆置(带头结点)
//自己写的,有待商榷,还未测试
LinkList List_Reverse(LinkList &L,LinkList LastL)
{
L= (LNode *)malloc(sizeof(LNode));
L->next=NULL;
LNode *p=LastL;
LNode *s;
while(p->next!=NULL)
{
s=(LNode *)malloc(sizeof(LNode));
p=p->next;
s->data=p->data;
s->next=L->next;
L->next=s;
free(p);
}
return L;
}
网上答案——头插法
void converse(LinkList *head)
{
LinkList *p,*q;
p=head->next;
head->next=NULL;
while(p)
{
q=p;
p-p->next;
q->next=head->next;
head->next=q;
}
}
网上答案——递归法
算法思想:先假定有一个函数,可以将以head为头结点的单链表逆序,并返回新的头结点。利用这个函数对问题进行求解;将链表分为当前头结点和其余部分,递归的过程就是,先将表头结点从链表中拆出来,然后对其余部分进行逆序,最后将当前的表头结点链接到逆序列表的尾部,递归的终止条件就是链表只剩一个结点时,直接返回该结点
/*
dfs深度遍历这个单链表
在递归的时候,不作处理
在回溯时,标记回溯开始的位置(新的头结点),将节点和节点的下一节点逆置,将新的头结点传递给上一层递归,
接下来逆置上一个节点和上一节点的下一个节点(逆置已逆置链表的尾节点),并传递标记的头结点,直到回溯完成
我们就得到了逆转的单链表和它的头结点
dfs(当前状态)
{
if(边界状态) //head==NULL说明链表为空,不用处理, 从头到尾我们需要处理的最后一个节点是倒数第二个节点,将它和倒数第一逆置,因此,遍历到倒是第一个节点就是边界条件
{
记录 //标记头结点位置并传递(这里选用返回值,还可以通过全局变量)给上一层递归
}
dfs(下一状态)
//回溯部分 //节点和下一节点(逆置这已逆置链表的尾结点和),将新的头结点传递给上一层递归
}
*/
ListNode *reverse(ListNode *head)
{
if(head==NULL || head->next ==NULL)
return head;
/*递归*/
ListNode* headOfReverse = reverse(head->next);
// cout<<head->next<<" "<<headOfReverse<<endl;
/*回溯:将当前表头结点链接到逆序链表的尾部*/
head->next->next = head;
head->next = NULL;
return headOfReverse;
}
单链表基本操作——删除
删除第i位置的结点,并返回该结点的值;先查找第i-1位置的结点(带头结点)
bool ListDelete(LinkList &L,int i;ElemType &e)
{
if(i<1) return false;
LNode *p;
int j=0;
p=L;
while(p!=NULL&&j<i-1)
{
p=p->next;
j++;
}
if(p==NULL) return false;
if(p->next == NULL) return false;
LNode *q=p->next;
e=q->data;
p->next=q->next;
free(q);
return true;
}
时间复杂度:最好,O(1);最坏,O(n);平均:O(n)
单链表基本操作——查找
按位查找(带头结点)
获取表L中第i个位置的元素结点
//按位查找,返回第i个元素
LNode * GetElem(LinkList L,int i)
{
if(i<0) return NULL;
LNode *p;
int j=0;
p=L;
while(p!=NULL && j<i)
{
p=p->next;
j++;
}
return p;
}
课本方法
LNode * GetElem(LinkList L, int i)
{
LNode *p;
int j=1;
p=L->next;
if(i==0) return L;
if(i<1)
return NULL;
while(p!=NULL && j<i)
{
p=p->next;
j++;
}
return p;
}
平均时间复杂度:O(n)
按值查找(带头结点)
在表L 中,找到数据域==e的结点
LNode * LocateElem(LinkList L,ElemType e)
{
LNode *p=L->next;
while(p!=NULL && p->data!=e)
p=p->next;
return p;
}
平均时间复杂度:O(n)
单链表基本操作——表长
int GetLinkListLen(LinkList L)
{
int len=0;
LNode *p=L->next;
while(p!=NULL)
{
len++;
p=p->next;
}
return len;
}
int length(LinkList L)
{
int len=0;
LNode *p=L;
while(p->next!=NULL)
{
p=p->next;
len++;
}
return len;
}
平均时间复杂度:O(n)
双链表的基本操作——创建
双链表的按值查找和按位查找与单链表操作相同
双链表的插入和删除与单链表操作不同
typedef struct DNode{
ElemType data;
struct DNode *prior,*next;
}DNode,*DLinklist;
//初始化双链表
bool InitDLinklist(DLinklist &L)
{
L=(DNode *)malloc(DNode);
if(L==NULL) return false;
L->prior=NULL;
L->next=NULL;
return true;
}
//判断双链表是否为空
bool Empty(DLinklist L){
if(L->next == NULL)
return true;
else
return false;
}
双链表的基本操作——插入
//后插
bool InsertNextNode(DNode *p,DNode *s)
{
if(p==NULL || s==NULL)
return false;
s->next=p->next;
if(p->next != NULL) //若p结点有后继结点
p->next->prior=s;
s->prior=p;
p->next=s;
return true;
}
//前插
//找到给定节点的前驱节点,对该前驱节点实现后插操作
bool InsertPriorDNode(DNode *p,DNode *s)
{
if(p==NULL || s==NULL)
return false;
DNode *q=p->prior;
InsertNextNode(q,s);
}
双链表的基本操作——删除
//删除p的后继节点s
bool DeleteDNode(DNode *p)
{
if(p== NULL)
return false;
DNode *q=p->next;
if(q==NULL) return false;
p->next=q->next;
if(q->next!=NULL)
q->next->prior=p;
free(q);
return true;
}
//循环释放各个数据结点
void DestroyList(DLinklist &L)
{
while(L->next != NULL)
DeleteDNode(L);
free(L);
L=NULL;
}
循环单链表——创建
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode,*LinkList;
//初始化一个循环单链表
bool InitList(LinkList &L)
{
L = (LNode *)malloc(sizeof(LNode));
if(L==NULL) return false;//内存不足,分配失败
L->next=L;//L的头结点next指向头结点
return true;
}
//判断循环单链表是否为空
bool Empty(LinkList L)
{
if(L->next==L)
return true;
else
return false;
}
//判断结点p是否为循环单链表的表尾结点
bool isTail(LinkList L,LNode *p)
{
if(p->next == L)
return true;
else
return false;
}
循环双链表——创建
头结点的prior指针指向尾结点;尾结点的next指针指向头结点
typedef struct DNode{
ElemType data;
struct DNode *prior,*next;
}DNode,*DLinklist;
bool InitDLinklist(DLinklist &L)
{
L=(DNode *)malloc(sizeof(DNode));
if(L==NULL) return false;
L->next=L;
L->prior=L;
return true;
}
//判断循环双链表是否为空
bool Empty(DLinklist L)
{
if(L->next==L)
return true;
else
return false;
}
//判断结点p是否为循环双链表的表尾结点
bool isTail(DLinklist L,DNode *p)
{
if(p->next==L)
return true;
else
return false;
}
循环双链表——插入
//后插
bool InsertNextNode(DNode *p,DNode *s)
{
if(p==NULL || s==NULL)
return false;
s->next=p->next;
p->next->prior=s;
s->prior=p;
p->next=s;
return true;
}
循环双链表——删除
//删除p的后继节点q
bool DeleteDNode(DNode *p)
{
if(p== NULL)
return false;
DNode *q=p->next;
if(q==NULL) return false;
p->next=q->next;
q->next->prior=p;
free(q);
return true;
}
静态链表的定义
第一种
#define MaxSize 10
typedef struct{
ElemType data;
int next;
}SLinkList[MaxSize];
//SLinkList b相当于定义了一个长度为MaxSize的Node型数组
void main()
{
SLinkList b;
}
第二种
#define MaxSize 10
struct Node{
ElemData data;
int next;
};
void main(){
struct Node a[MaxSize];
}
静态链表基本操作
初始化:
1、把a[0]的next设为-1
2、把其他结点的next设为一个特殊值用来表示结点空闲,如-2
插入位序为i的结点:
1、找到一个空结点(若结点next值为-2,则该结点为空),存入数据元素
2、从头结点出发找到位序为i-1的结点
3、修改新结点的next
4、修改i-1号结点的next
删除某个结点:
1、从头结点除法找到前驱结点
2、修改前驱节点的游标
——插入
//后插
bool InsertNextNode(DNode *p,DNode *s)
{
if(p==NULL || s==NULL)
return false;
s->next=p->next;
p->next->prior=s;
s->prior=p;
p->next=s;
return true;
}
循环双链表——删除
//删除p的后继节点q
bool DeleteDNode(DNode *p)
{
if(p== NULL)
return false;
DNode *q=p->next;
if(q==NULL) return false;
p->next=q->next;
q->next->prior=p;
free(q);
return true;
}
静态链表的定义
第一种
#define MaxSize 10
typedef struct{
ElemType data;
int next;
}SLinkList[MaxSize];
//SLinkList b相当于定义了一个长度为MaxSize的Node型数组
void main()
{
SLinkList b;
}
第二种
#define MaxSize 10
struct Node{
ElemData data;
int next;
};
void main(){
struct Node a[MaxSize];
}
静态链表基本操作
初始化:
1、把a[0]的next设为-1
2、把其他结点的next设为一个特殊值用来表示结点空闲,如-2
插入位序为i的结点:
1、找到一个空结点(若结点next值为-2,则该结点为空),存入数据元素
2、从头结点出发找到位序为i-1的结点
3、修改新结点的next
4、修改i-1号结点的next
删除某个结点:
1、从头结点除法找到前驱结点
2、修改前驱节点的游标
3、被删除结点next设为-2