目录
线性表
类型名称:线性表
数据对象集:线性表是n(>=0)个元素构成的有序序列(a1,a2,a3,a4.........)
操作集:线性表L属于List,元素X属于Elemtype。
顺序表的优点:
1.存储密度大,节省存储空间
2.访问效率高,可随机访问。
设a1的存储地址为loc(a1),每个数据占用d个字节,则第i各元素的地址为:loc(ai)=loc(a1)+(i-1)*d
3.存储实现简单,容易实现
顺序表的缺点:
1.连续性:需要占用连续的存储区域
2.静态性:大小确定后,程序运行中一般不允许改变
3.运算不方便,插入或者删除都需要移动大量的元素
线性表的基本操作有:
1.List Makeempty():初始化一个空的线性表
2.Elemtype FindKth(int k,List L):根据位序k,返回相应的元素
3.int find(ElemType x,List L):在线性表中查找x第一次出现线的位置
4.void insert(ElemType x,int i,List L):在位序i前插入一个新的元素
5.void delete(int i,List L):删除指定i的元素
6.int length(List L):返回线性表L的长度
顺序表的定义类型:
#define MaxSize 100 //最大长度
type struct
{
ElemType *Elem; //指向数据元素的基地址
int length; //线性表的当前长度
} sqlist;
初始化线性表L:
Status InitList_Sq(SqList&L) //构造一个空的顺序表
{
L.Elem= new ElemType[MaxSize];//为顺序表分配空间
/*L.Elem=(*ElemType)malloc(sizeof(ElemType)*MaxSize)*/
if(!L.elem) exit(OVERFLOW); //存储分配失败
L.Length=0; //空表长度为0
return OK;
}
参数用指针
Status InitList_Sq(SqList*L) //构造一个空的顺序表
{
L->Elem= new ElemType[MaxSize];//为顺序表分配空间
/*L.Elem=(*ElemType)malloc(sizeof(ElemType)*MaxSize)*/
if(!L->elem) exit(OVERFLOW); //存储分配失败
L->Length=0; //空表长度为0
return OK;
}
几个基本的操作:
简单基本操作的实现:
求线性表的长度:
int GetLength
{
return (L.Length);
}
判断线性表是否为空:
int IsEmpty(SqList)
{
if(L.Length==0)
return 1;
else
return 0;
}
销毁线性表:
void DestroyList(SqList&L)
{
if(L.elem) delete[L.elem];//释放存储空间
}
清空线性表
void ClearList(SqList&L)
{
L.length=0; //将线性表的长度置为0
}
获取列表中第i元素的内容:
int GetElem(SqList l,int i,ElemType &e)
{
if(i<1||i>L.Length) return error;//判断i是否合理
e=L.elem[i-1];//第i个元素的存储位置在第i-1
return OK;
}
在线性表中查找值为e的数据元素的位置(不是存储位置):
int LocateElem(ElemType e,SqList l)
{
for(i=0;i<L.Length;i++)
if(L.elem[i]==e) return i+1;
return 0;
}
在第i个元素上插入(应该移动l.length-(n-1)次):
Status ListInsert_Sq(Sqlist&L,int i,ElemType e)
{
if(i<1||i>L.Length) return error; //判断是否合法
if(L.Length==MaxSize) return error; //判断存储空间是否满了
for(j=L.Length;j>i-1;j--)
L.elem[j+1]=l.elem[j]; //插入位置以及插入位置之后的元素后移一个存储元素
L.elem[i-1]=e; //将新元素放入第i个位置
++L.Length; //表的长度增加1
return OK;
}
最快:插入到最后一个
最慢:插入到第一个,需要移动n个元素
共有n+1种位置,能插入元素。
AMN=n/2
删除第i个结点:
Status ListDelete_Sq(SqList &L,int i)
{
if(i<1||i>L.Length) return error;
for(j=i;j<L.Length;j++)
L.Length[j-1]=L.Length[j];//被删除元素之后的元素,往前移
--L.Length; //表的长度减1
return OK;
}
最快:删除最后一个,不需要移动
最慢:删除首节点,需要移动n-1个元素
各种位置删除共有n种可能
AMN=(n-1)/2
顺序表的时间复杂度S(n)=O(1) 没有占用辅助空间
线性表的链式存储结构
回顾:线性表逻辑上和物理上的两个元素之间是相邻的。
顺序表的链式结构:逻辑上相邻但物理上不一定相邻。又被称为 非顺序映像或者链式映像。
链表由结点组成,结点是由数据域(存储元素数值数据)和 指针域(存储直接后继节点的存储位置)组成。
单链表(线性链表):结点只有一个指针域的链表。
双链表:有两个指针域的链表。
循环链表:首尾相接的链表。
头指针:指向链表中第一个结点的指针。
首元结点:链表中存储第一个数据元素a1的结点
头结点:在链表的首元结点之前附设一个节点;数据域内只放空表标志和表长等信息。(此节点不计入链表的长度)
链表分为有头节点和无头节点。
链表的优点:
结点在存储器中的位置是随意的,即逻辑上相邻但物理上不一定相邻。
访问时只能通过头指针进入链表,并通过每个结点的指针域向后扫描其余结点,所以寻找第一个结点和寻找最后一个结点所花费的时间不相同。
链表的缺点:
存储密度小
存储效率不高,必须采用顺序存取(即存储元素时,只能按照链表的顺序进行访问(顺藤摸瓜))。
单链表:
表头唯一,因此单链表用头指针的名字来命名
typedef struct Lnode
{
ElemType data; //数据域
struct LNode *next; //指针域
} LNode,*LinkList; //*LinkList为Lnode类型的指针
注意:指针变量p:表示节点的地址(LNode*p)。节点变量*p:表示一个节点
单链表的基本操作:
初始化:
Status InitList_L(LinkList &L){
L=new LNode; //L=(LinkList) malloc(sizeof(LNode));
L->next=NULL;
return OK;
}
删除:
Status DestroyList_L(LinkList &L)
{
LinkList p;
while(L)
{
p=L;
L=L->next;
delete p;
}
return OK;
}
清空:
Status ClearList(LinkList & L){
// 将L重置为空表
LinkList p,q;
p=L->next; //p指向第一个结点
while(p) //没到表尾
{ q=p->next; delete p; p=q; }
L->next=NULL; //头结点指针域为空
return OK;
}
判断表是否为空:
int ListEmpty(LinkList L)
{
//若L为空表,则返回1,否则返回0
if(L->next) //非空
return 0;
else
return 1;
}
求表长:
int ListLength_L(LinkList L){
//返回L中数据元素个数
LinkList p;
p=L->next; //p指向第一个结点
i=0;
while(p){//遍历单链表,统计结点数
i++;
p=p->next; }
return i;
}
单链表的重要操作:
获取线性表L中的某个数据元素的内容
Status GetElem_L(LinkList L,int i,ElemType &e){
p=L->next;j=1; //初始化
while(p&&j<i){ //向后扫描,直到p指向第i个元素或p为空
p=p->next; ++j;
}
if(!p || j>i)return ERROR; //第i个元素不存在
e=p->data; //取第i个元素
return OK;
}//GetElem_L
//在线性表L中查找值为e的数据元素
LNode *LocateELem_L (LinkList L,Elemtype e) {
//返回L中值为e的数据元素的地址,查找失败返回NULL
p=L->next;
while(p &&p->data!=e)
p=p->next;
return p;
}
//在线性表L中查找值为e的数据元素
int LocateELem_L (LinkList L,Elemtype e) {
//返回L中值为e的数据元素的位置序号,查找失败返回0
p=L->next; j=1;
while(p &&p->data!=e)
{p=p->next; j++;}
if(p) return j;
else return 0;
}
插入:
//在L中第i个元素之前插入数据元素e
Status ListInsert_L(LinkList &L,int i,ElemType e){
p=L;j=0;
while(p&&j<i−1){p=p->next;++j;} //寻找第i−1个结点
if(!p||j>i−1)return ERROR; //i大于表长 + 1或者小于1
s=new LNode; //生成新结点s
s->data=e; //将结点s的数据域置为e
s->next=p->next; //将结点s插入L中
p->next=s;
return OK;
}//ListInsert_L
单链表的建立:
头插法:
void CreateList_F(LinkList &L,int n){
L=new LNode;
L->next=NULL; //先建立一个带头结点的单链表
for(i=n;i>0;--i){
p=new LNode; //生成新结点
cin>>p->data; //输入元素值
p->next=L->next;L->next=p; //插入到表头
}
}//CreateList_F
尾插法:
void CreateList_L(LinkList &L,int n){
//正位序输入n个元素的值,建立带表头结点的单链表L
L=new LNode;
L->next=NULL;
r=L; //尾指针r指向头结点
for(i=0;i<n;++i){
p=new LNode; //生成新结点
cin>>p->data; //输入元素值
p->next=NULL; r->next=p; //插入到表尾
r=p; //r指向新的尾结点
}
}//CreateList_L
删除:删除第i个结点
找到ai-1的存储位置;
临时保存结点ai的地址在q中,以便释放;
领p->next指向ai的直接后继结点;
将ai保存在e中;
释放ai的空间
删除
Status ListDelete_L(LinkList &L,int i,ElemType &e)
{
p=L;j=0;
while(p->next&&j<i-1) //寻找第i个结点
{
p=p->next;++j; //命令p指向前驱结点
}
if(!(p->next)||j>j-1) return error;
q=p->next; //临时保存被删除结点的地址,以便于释放
p->next=q->next; //改变删除结点前驱结点的指针域
e=q->data; //保存删除结点的数据域
delete q; //删除结点的空间
return OK;
}
查找:时间复杂度O(n)
删除和插入:时间复杂度O(1)
循环链表:
循环链表的任何一个结点的位置都可以找到其他的结点。将表中的尾结点的指针域指向头结点。
只有尾指针的循环单链表更方便找出第一个和最后一个结点。
没有空指针域;开始结点:rear->next->next 终端结点:rear
LinkList Connect(LinkList Ta,LinkList Tb)
{//假设Ta,Tb都是非空的循环单链表
p=Ta->next; //1.p存放表头结点
Ta->next=Tb->next; //2.Tb的表头连接Ta表尾
delete Tb->next; //3.释放Tb表头结点
Tb->next=p; //4.修改指针
return Tb;
}
经典例题:约瑟夫问题
双向链表:
定义:每个结点有两个指针,一个指向前驱结点一个指向后继结点。
双链表的插入:
Status ListInsert_DuL(DuLinkList &L,int i,ElemType e){
if(!(p=GetElemP_DuL(L,i))) return ERROR;
s=new DuLNode;
s->data=e;
s->prior=p->prior;
p->prior->next=s;
s->next=p;
p->prior=s;
return OK;
}
双链表的删除:
Status ListDelete_DuL(DuLinkList &L,int i,ElemType &e)
{
if(!(p=GetElemP_DuL(L,i))) return ERROR;
e=p->data;
p->prior->next=p->next;
p->next->prior=p->prior;
delete p;
return OK;
}
顺序表和链表的比较:
图的遍历
概念:从给定的图的某个顶点出发,按照某种搜索方法沿着图中的所有顶点,是每个顶点被访问一次。
图的遍历有两种 :深度优先遍历(DFS),广度优先遍历(BFS)
深度优先遍历(DFS)
体现出先进后出的特点:用栈或者递归方式实现
DFS:1->2->4 用栈保存访问过的节点
如何判断结点是否被访问过?
设置一个visited[]全局数组,visited[]=0 顶点i没有被访问,visited[]=1顶点i被访问过了
//采用邻接表的DFS算法:
void DFS(ALGraph*G,int v)
{
ArcNode*p;int w;
visited[v]=1; //置已访问标记
printf("%d",v); //输出被访问顶点的标号
p=G->adjist[v].firstarc;//p指向顶点v的第一条边的头结点
while(p!=NULL)
{
w=p->adjvex;
if (visited[w]==0)
DFS (G,w); //若w顶点未访问,递归访问他
p=p->nextarc; //p指向顶点v的下一条边的边头节点
}
}
时间复杂度是O(n+e)
广度优先遍历(BFS)
体现出先进先出的特点:用队列实现
BFS:1->2->3->0 用队列保留访问过的节点
void BFS(ALGraph*G,int v)
{
ArcNode*p;int w,i;
int queue[MAXV],front=0,rear=0;//定义循环队列
int visited[MAXV];
for(i=0;i<G->n;i++)
visited[i]=0;//访问标志数组初始化
printf("%2d",v);//输出被访问顶点的编号
visited[v]=1; //置已访问标记
rear=(rear+1)%MAXV;
queue[rear]=v; //v进队
}