线性表是最常用最典型的线性结构。
简易目录:
线性表:
逻辑特征
线性表的类型定义
存储结构
顺序存储表示
元素存储位置的计算
顺序表的基本操作实现
顺序表各算法时间复杂度的计算
C++中的参数传递
链式存储表示
单链表
双向链表
循坏链表
单链表,循坏链表和双向链表的时间效率的比较
顺序表和链表的比较
线性表的合并
用顺序表实现
用链表实现
可以看到,线性表是最基础,也是最简单的了。所以我们从线性表开始学习。
定义:具有相同特性的数据元素的一个有限序列,例如:
逻辑特征:
线性表的类型(即抽象数据类型,也称数据类型)定义:
上述的基本操作是逻辑结构上定义的运算,那么如何实现这些基本运算,就要先确定其存储结构。
线性表的存储以及在存储结构上各操作的实现:
线性表的基本操作:
操作算法中用到的预定义变量和类型:
线性表的存储结构有四种,其中最基本两种的存储结构:顺序存储结构和链式存储结构
线性表的顺序存储表示(也称为顺序映像):
定义:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中。
也就是说线性表顺序存储我们可以用一维数组来表示,但由于数组长度不可动态定义
即:
//错误写法
int n;
scanf("%d",&n);
int a[n];
这样是不可行的,所以要想用一维数组来表示,就必须再定义一个长度,像以上一样用结构体,在里面定义一个长度就好了。
元素存储位置的计算:
我们一般用的都是第二个公式,因为一般第一个元素的地址(即基地址)我们都知道。
顺序表的基本操作实现:
#include<stdio.h>
#include<iostream>
using namespace std;
//1.malloc(m):开辟m字节长度的地址空间,并返回这段空间的首地址
//2.sizeof(x):计算变量x的长度
//3.free(p)函数:释放指针p所指变量的存储空间,即彻底删除一个变量
//以上3个函数都需用到下面的这个头文件
#include<stdlib.h>
//函数的结果状态代码
#define OVERFLOW -2
#define OK 1
#define TRUE 1
#define FALSE 0
#define ERROR 0
//定义
#define MAXSIZE 100
typedef struct{
int *elem; //(数组动态分配)//int elem[MAXSIZE];(数组静态分配)
int length;
}SqList;
int InitList(SqList &L);//初始化操作,建立一个空的线性表L
void DestroyList(SqList &L);//销毁线性表L
void ClearList(SqList &L);//清空线性表L
int ListInsert(SqList &L,int i,int e);//在线性表L中第i个位置插入新元素e
int ListDelete(SqList &L,int i,int &e);// 删除L中第i个位置的元素,并用e返回其值
bool ListEmpty(SqList L);//判断线性表是否为空,若为空,返回true,否则,返回false
int ListLength(SqList L);//返回线性表L中的元素个数
int LocateElem(SqList L,int e);//L中查找与给定值e相等的元素,若成功,则返回该元素在表中的序号,否则返回0
int GetElem(SqList L,int i,int &e);//将线性表L中第i个位置的元素返回给e
int main()
{
SqList L;//创建线性表L
int m=InitList(L);
if(m)
cout<<"成功创建空表L"<<endl;
ListInsert(L,1,3);
cout<<"已成功插入"<<endl;
cout<<"插入后的长度:"<<L.length<<endl;
ListInsert(L,2,6);
cout<<"已成功插入"<<endl;
cout<<"插入后的长度:"<<L.length<<endl;
int e;
ListDelete(L,1,e);
cout<<"被删除的元素:"<<e<<endl;
cout<<"删除后的长度:"<<L.length<<endl;
ListEmpty(L);
if(ListEmpty(L))
cout<<"线性表L为空"<<endl;
else
cout<<"线性表L非空"<<endl;
int n;
ListLength(L);
cout<<"此时线性表的长度:"<<ListLength(L)<<endl;
int a=6;
LocateElem(L,a);
cout<<"该元素的序号为:"<<LocateElem(L,a)<<endl;
GetElem(L,1,e);
cout<<"线性表L的第1个元素:"<<e<<endl;
ClearList(L);
cout<<"线性表L已清空"<<endl;
DestroyList(L);
cout<<"线性表L已销毁"<<endl;
return 0;
}
//初始化(即建立一个空的顺序表)
int InitList(SqList &L)
{
L.elem=new int[MAXSIZE]; // 用C++来为顺序表分配空间
// L.elem=(int*)malloc(sizeof(int)*MAXSIZE); //用C语言来为顺序表分配空间
if(!L.elem) exit(OVERFLOW); //exit表程序终止 OVERFLOW可看作“溢出”
L.length=0;
return OK;
}
//销毁线性表L
void DestroyList(SqList &L)
{
if(L.elem) delete L.elem;
}
//清空线性表L
void ClearList(SqList &L)
{
L.length=0;
}
//在线性表L中第i个位置插入新元素e
int ListInsert(SqList &L,int i,int e)
{
if(i<1||i>L.length+1) return ERROR;//插入位置不合法,可以插入在第1到n个位置,也可以插入到表最后(即第n+1个位置)
if(L.length==MAXSIZE) return ERROR;//当前存储空间已满
for(int j=L.length-1;j>=i-1;j--)
{
L.elem[j+1]=L.elem[j]; //插入位置及以后的元素后移
}
L.elem[i-1]=e; //将新元素放到第i个位置上,即下标为i-1的位置上
L.length++;
//平均移动次数n/2,时间复杂度O(n)
}
//删除L中第i个位置的元素,并用e返回其值
int ListDelete(SqList &L,int i,int &e)
{
if( (i<1) || (i>L.length) ) return ERROR;
e = L.elem[i-1];
for(int j=i ;j<=L.length-1 ;j++)
L.elem[j-1]=L.elem[j]; //被删除元素之后的元素都前移
--L.length;
return OK;
//平均移动次数(n-1)/2,时间复杂度O(n)
}
//判断线性表是否为空
bool ListEmpty(SqList L)
{
if(L.length==0) return true;
else
return false;
}
//返回线性表L中的元素个数
int ListLength(SqList L)
{
return(L.length);
}
//在L中查找与e相等的元素,并返回该元素在表中的序号
int LocateElem(SqList L,int a)
{
for(int i;i<L.length;i++)
if(L.elem[i]==a) return i+1; //查找成功,返回序号
return 0; //查找失败,返回0
//平均查找次数(n+1)/2,时间复杂度O(n)
}
//将线性表L中第i个位置的元素返回给e
int GetElem(SqList L,int i,int &e)
{
if(i < 1 || i > L.length) return ERROR; //i值不合理
e=L.elem[i-1];
return OK;
//时间复杂度O(1)
}
运行结果:
顺序表各算法时间复杂度的计算:
1.插入:
所以时间复杂度就为O(n)。(如果不懂,可以参考第一章绪论关于时间复杂度的分析)
2.删除:
所以时间复杂度为O(n)。
3.查找:
时间复杂度O(n)。
同理:
若查找的是第一个元素,则查找1次就行了
若查找的是第n个元素或查找不成功,则都需要查找n次
平均查找次数:(1/n)*((1+n)*n)/2)=(n+1)/2
C++中的参数传递:(主要解释代码实现中&的含义)
1.指针变量做参数:
#include<iostream>
using namespace std;
void swap(int *m,int *n);
void swap2(int *x,int *y);
int main()
{
int a=3,c=3;
int b=5,d=5;
swap(&a,&b);
swap2(&c,&d);
cout<<a<<" "<<b<<endl;
cout<<c<<" "<<d<<endl;
return 0;
}
//形参变化影响实参
void swap(int *m,int *n)
{
int t;
t=*m;
*m=*n;
*n=t;
}
//形参变化不影响实参
void swap2(int *x,int *y)
{
int *t;
t=x;
x=y;
y=t;
}
运行结果:
2.数组名作参数:
#include<iostream>
using namespace std;
void sub(char b[]);
int main()
{
char a[10]="hello";
sub(a);
cout<<a<<endl;
}
void sub(char b[])
{
b[0]='w';
}
运行结果:
3.引用类型做参数:
#include<iostream>
using namespace std;
void swap(int &m,int &n);
int main()
{
int a=3;
int b=5;
swap(a,b);
cout<<a<<" "<<b<<endl;
}
void swap(int &m,int &n)
{
int t;
t=m;
m=n;
n=t;
}
运行结果:
可见,我们要修改原来的值时,可用& ,就如顺序表代码实现时,用&L和L的区别就在于L是否被修改。
线性表的链式存储表示:
定义:
单链表:
头结点的作用:
头结点的数据域:
单链表的特点:
单链表的基本操作以及代码实现:(带头结点)
#include<iostream>
using namespace std;
//函数的结果状态代码
#define OK 1
#define ERROR 0
typedef int ElemType ;//ElemType就相当于int
//定义
typedef struct LNode{ //声明结点的类型和指向结点的指针类型
ElemType data; //结点的数据域
struct LNode *next; //结点的指针域 ,指向的仍然是这样的一个结构体
}LNode,*LinkList; //LinkList为指向结构体Lnode的指针类型
int InitList(LinkList &L);//单链表的初始化,即构造一个空表
void CreatList(LinkList &L,int n);//头插法建立单链表
void CreatList2(LinkList &L,int n);//尾插法建立单链表
int DestroyList(LinkList &L);//销毁单链表L
int ClearList(LinkList &L);//清空单链表L
int ListInsert(LinkList &L,int i,int e);//在单链表L中第i个元素之前插入新元素e
int ListDelete(LinkList &L,int i,int &e);// 删除L中第i个数据元素,并用e返回其值
bool ListEmpty(LinkList L);//判断单链表是否为空,若为空,返回true,否则,返回false
int ListLength(LinkList L);//求单链表的表长
int LocateElem(LinkList L,int e);//L中查找与给定值e相等的元素,若成功,则返回该元素在表中的序号,否则返回0
LNode *LocateElem2(LinkList L,int e);//L中查找与给定值e相等的元素,若成功,则返回该元素的地址,否则返回NULL
int GetElem(LinkList L,int i,int &e);//将单链表L中第i个位置的元素返回给e
int main()
{
LinkList L; //或LNode *L;
int m=InitList(L);
if(m)
cout<<"成功创建空表L"<<endl;
CreatList(L,2);
cout<<"头插法创建单链表成功"<<endl;
CreatList2(L,2);
cout<<"尾插法创建单链表成功"<<endl;
ListInsert(L,1,6);
cout<<"已成功插入"<<endl;
ListInsert(L,2,9);
cout<<"已成功插入"<<endl;
int e;
ListDelete(L,1,e);
cout<<"被删除的元素:"<<e<<endl;
ListEmpty(L);
if(ListEmpty(L))
cout<<"单链表L为空"<<endl;
else
cout<<"单链表L非空"<<endl;
ListLength(L);
cout<<"此时单链表的长度:"<<ListLength(L)<<endl;
int a=9;
LocateElem(L,a);
cout<<"该元素的位置为:"<<LocateElem(L,a)<<endl;
int b=9;
LocateElem2(L,b);
cout<<"该元素的地址为:"<<LocateElem2(L,b)<<endl;
GetElem(L,1,e);
cout<<"单链表L的第1个元素:"<<e<<endl;
ClearList(L);
cout<<"单链表L已清空"<<endl;
DestroyList(L);
cout<<"单链表L已销毁"<<endl;
return 0;
}
//单链表的初始化,即构造一个空表
int InitList(LinkList &L)
{
L=new LNode; //或L=(LinkList)malloc(sizeof(LNode));
L->next=NULL;
return OK;
}
//头插法建立单链表
void CreatList(LinkList &L,int n)
{
for(int i=n;i>0;--i)
{
LinkList p;
p=new LNode;
cin>>p->data;
p->next=L->next;
L->next=p;
}
}
//尾插法建立单链表
void CreatList2(LinkList &L,int n)
{
LinkList r;//尾指针
r=L;//尾指针指向头结点
for(int i=0;i<n;++i)
{
LinkList p;
p=new LNode;
cin>>p->data;
p->next=NULL;
r->next=p;
r=p;
}
}
//判断链表是否为空 ,若L为空表,则返回1,否则返回0
bool ListEmpty(LinkList L)
{
if(L->next) //非空
return 0;
else
return 1;
}
//销毁单链表L
int DestroyList(LinkList &L)//(从头指针开始,依次释放所有结点)
{
LinkList p;//或LNode *p;
while(L)
{
p=L;
L=L->next;
delete p;
}
return OK;
}
//清空单链表L (链表依然存在,但无元素,头指针和头结点仍然存在)
int ClearList(LinkList &L)//(依次释放所有结点,并将头结点指针域设置为空)
{
LNode *p,*q;
p=L->next;//从首元结点开始
while(p)
{
q=p->next;
delete p;
p=q;
}
L->next=NULL;
return OK;
}
//在单链表L中第i个元素之前插入新元素e
int ListInsert(LinkList &L,int i,int e)
{
LinkList p;
p=L;
int j=0;
while(p&&j<i-1)
{
p=p->next;
++j;
}
if(!p||j>i-1) return ERROR;
LinkList s;
s=new LNode;
s->data=e;
s->next=p->next;
p->next=s;
return OK;
}
// 删除L中第i个数据元素,并用e返回其值
int ListDelete(LinkList &L,int i,int &e)
{
LinkList p,q;
p=L;
int j=0;
while(p->next&&j<i-1) //寻找第i个结点,并令p指向其前驱
{
p=p->next;
++j;
}
if(!p->next||j>i-1) return ERROR;
q=p->next;//保存被删结点的地址以备释放
p->next=q->next;//改变被删节点前驱结点的指针域
e=q->data;
delete q;
return OK;
}
//求单链表的表长
int ListLength(LinkList L)
{
LinkList p;
p=L->next;
int i=0;
while(p)
{
i++;
p=p->next;//遍历单链表,统计结点数
}
return i;
}
//L中查找与给定值e相等的元素,若成功,则返回该元素在表中的序号,否则返回0
int LocateElem(LinkList L,int e)
{
LinkList p;
int j=1;
p=L->next;
while(p&&p->data!=e)//循环结束条件:p为空时或找到时
{
p=p->next;
j++;
}
if(p) return j;//若找到了,返回其位置
else
return 0;//若没找到,返回0
}
//L中查找与给定值e相等的元素,若成功,则返回该元素的地址,否则返回NULL
LNode *LocateElem2(LinkList L,int e)
{
LinkList p;
p=L->next;
while(p&&p->data!=e)//循环结束条件:p为空时或找到时
{
p=p->next;
}
return p;//若找到了,则直接返回地址,若没找到,则此时p为空了已经,返回空就行
}
//将单链表L中第i个位置的元素返回给e
int GetElem(LinkList L,int i,int &e)
{
LinkList p;
p=L->next;
int j=1;
while(p&&j<i)//p不为空,j还没到i
{
p=p->next;
++j;
}
if(!p||j>i) return ERROR;//没有找到,元素不存在
e=p->data;
return OK;
}
运行结果:
算法时间复杂度分析:
双向链表:
定义:
双向链表的操作以及实现:(除插入和删除操作,其他的操作与上述单链表的操作基本一致)
双向链表的插入:
s->prior=p->prior //将p的前驱结点地址存到s的前指针域
p->prior->next=s //将p的前驱结点的指针域指向s
s->next=p //给s的next赋值p
p->prior=s //将p的前指针域指向s
双向链表的删除:
p->prior->next=p->next //让指针变量p的前驱结点的指针域指向其后继结点
p->next->prior=p->prior //将指针变量p的前驱结点的地址存到其后继结点的前指针域
循坏链表:
定义:
循坏链表的合并:
带尾指针循坏链表的合并的操作:
单链表,循坏链表和双向链表的时间效率的比较:
顺序表和链表的比较:
线性表的合并:
有序表合并--用顺序表实现:
有序表的合并--用链表实现: