线性表
主要内容
2.1 线性表的定义和特点
2.2 线性表的顺序表示和实现
2.3 线性表的链式表示和实现
2.4 顺序表和链表的比较
补充知识点:参数的值传递
参数传递有两种方式 :
(1)传值方式(参数为整型、实型、字符型等)
(2)传地址 l 参数为指针变量 l 参数为引用类型 l 参数为数组名
参数的值传递:
#include <iostream.h>
void swap(int m,int n)
{ int temp;
temp=m;
m=n;
n=temp;
}
void main() {
int a,b;
cin>>a>>b;
swap(a,b);
cout<<a<<endl<<b<<endl;
}
能否实现预想功能?
形式参数的值的改变并不改变实际参数的值!!
参数的指针变量传递:
#include <iostream.h>
void swap(int *m,int *n)
{ int temp;
temp=*m;
*m=*n;
*n=temp;
}
void main()
{ int a,b,*p1,*p2;
cin>>a>>b;
p1=&a;
p2=&b;
swap(p1, p2);
cout<<a<<endl<<b<<endl;
}
因为实际传递的是地址,所以输出时a,b的值交换了!
C++参数的引用类型传递:
#include<iostream.h>
void main( )
{ int i=5;
int &j=i;
i=7;
cout<<"i="<<i<<" j="<<j;
}
j是一个引用类型, 代表i的一个替代名
i值改变时, j值也跟着改变,所以会输出 i=7 j=7!!
C++参数的引用类型传递:
#include <iostream.h>
void swap(int&m, int&n)
{ int temp;
temp=m;
m=n;
n=temp;
}
void main()
{ int a,b;
cin>>a>>b;
swap(a,b);
cout<<a<<endl<<b<<endl;
}
(1)传递引用给函数与传递指针的效果是一样的,形参变化实参也发生变化
(2)指针参数虽然也能达到与使用引用的效果,但在被调函数中需要重复使用“*指针变量名”的形式进行运算,容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参
(3)引用类型作形参,在内存中并没有产生实参的副本,它直接对实参操作; 而一般变量作参数,形参与实参就占用不同的存储单元,所以形参变量的值是实参变量的副本
因此,当参数传递的数据量较大时,用引用比用一般变量传递参数的时间和空间效率都好
2.2线性表的定义和特点
一、线性表的定义
1、线性表:n(n≥0)个数据元素的有限序列,可记为:
List=(a1,…,ai,ai+1,…, an)
2、线性表的特点:
1)线性表中所有的数据元素属同一个数据对象;
2)线性表的相邻元素之间是一对一的序偶关系,体现在:
a、有且仅有一个“第一个”数据元素;
b、有且仅有第一个“最后一个元素”;
c、除第一个元素之外,每个数据元素均有且只有第一个前驱;
d、除最后一个元素之外,每个数据元素均有且只有一个后继
3、线性表中数据元素的个数n(n≥0)称为线性表的长度; 当线性表的长度n=0 时,称为空表。
4、ai 是第i (1≤ i ≤n) 个数据元素,称i为ai 在线性表中的位序
二、线性表的基本操作
1、初始建表 InitList(&L)
2、销毁线性表 DestroyList(&L)
3、置空表 ClearList(&L)
4、测试空表 ListEmpty(L)
5、求表长 ListLength(L)
6、访问元素 GetElem(L,i,&e)
7、定位元素 LocateElem(L,e)
8、插入元素 ListInsert(&L,i,e)
9、删除元素 ListDelete(&L,i,e)
10、查找前驱 PriorElem(L,Cur_e,&pre_e)
11、查找后继 NextElem(L,Cur_e,&next_e)
一、基本思想
1、用一组地址连续的存储单元依次存储线性表中的各个数据元素
2、特点 要求连续的存储空间;
用存储位置相邻表示数据元素的前驱和后继关系,即为逻辑相邻 、物理也相邻
顺序存储
随机存取
Loc(i)=Lo+(i-1)*m
存储密度
d=结点数据/结点结构=1
二、线性表的顺序存储表示
用一维数组描述顺序表
#define MAXSIZE 100
typedef int ElemType;
typedef struct {
ElemType *elem;
int length; // 数据元素个数
} SqList; //定义数据类型
SqList L; //定义变量
三、顺序表基本操作的实现
初始化顺序表
Status InitList(SqList &L)
{ L.elem=new ElemType[MAXSIZE];
if (!L.elem)
exit (OVERFLOW);
L.length=0;
return OK;
}//构造空的顺序表L
创建顺序表
Status CreateList(SqList &L, int n )
{ int i;
L.length = n;
L.elem = new ElemType[MAXSIZE];
if (!L.elem)
exit(OVERFLOW);
for (i=0; i<n;i++)
cin>>L.elem[i];
return OK;
}//初始化顺序表并赋值
清空线性表
void ClearList(SqList &L)
{
L.length=0; //将线性表的长度置为0
}
销毁线性表
void DestroyList(SqList &L)
{
if (L.elem)
delete L.elem; //释放存储空间
}
顺序表的查找
①按位序查找,返回元素值;
②按值查找,找到:返回位序;否则:返回0
顺序表有两种表示方法:数组表示法;指针表示法
按位序查找
//数组法
int GetElem(SqList L, int i, ElemType &e)
{ // i表示位序
if (i<1||i>L.length)
return ERROR;
e=L.elem[i-1];
return 0;
} //T(n)=O(1)
//指针法
int GetElem(SqList L, int i, ElemType &e)
{ // i表示位序
if (i<1||i>L.length)
return ERROR;
ElemType *p=&L.elem[i-1];
e=*p;
return 0;
} //T(n)=O(1)
按值查找
//数组法
int LocateElem(SqList L, ElemType e)
{ int i;
for (i=0;i<L.length;i++)
if (L.elem[i]==e)
return i+1;
return 0; //i表示下标
}
//指针法
int LocateElem(SqList L, ElemType e)
{ int i=1;
ElemType *p;
p=L.elem; //p=&L.elem[0];
while (i<=L.length&&e!=*p++)
++i;
if (i<=L.length)
return i;
else
return 0;
}
平均查找长度
查找成功的平均比较次数 (pi为各项的查找概率)
若查找概率相等,则
查找不成功,最坏情况比较 n 次
按位序插入元素
线性表:(a1,…,ai-1,ai,…,an)
插入元素e:(a1,…,ai-1,e,ai,…,an)
完成:
(1)判断插入位置是否合法;
(2)判断是否已满,满则返回ERROR;
(3)将第i…n个元素依次后移一位;
(4)在第i个位置处插入新元素e;
(5)表长加1,返回状态信息
Status ListInsert(SqList &L,int i,ElemType e)
{ //在顺序表第i个元素前插入新元素
if (i<1 || i>L.length+1) //插入的位置i太小或者太大
return ERROR;
if (L.length==MAXSIZE) //线性表满了
return ERROR;
for (j=L.length-1; j>=i-1; j--)
L.elem[j+1]= L.elem[j];
L.elem[i-1]=e;
++L.length;
return OK;
}
按位序删除元素
线性表:(a1,…,ai-1,ai,ai+1,…,an)
删除元素ai :(a1,…,ai-1, ai+1,…,an)
完成:
(1)判断删除位置是否合法;
(2)判断线性表是否为空;
(3)将第i+1…n个元素依次前移一位;
(4)表长减1,返回成功状态值
Status ListDelete(SqList &L,int i)
{ //在顺序表中删除第i个元素
if (i<1||i>L.length) //判断i的位置是否合法
return ERROR; // i是位序
if (L.length==0)
return ERROR;
for (j=i;j<=L.length-1;j++)
L.elem[j-1]=L.elem[j];
--L.length;
return OK;
}
插入和删除算法时间分析
(1)用“移动结点的次数”来衡量时间复杂度
(2)插入元素
最好: i=n+1,移动次数为0
最坏:i=1,移动次数为n
平均:等概率情况下,平均移动次数为 n/2
(3)删除元素
最好: i=n,移动次数为0
最坏:i=1,移动次数为n-1
平均:等概率情况下,平均移动次数(n-1)/2
2.3 线性表的链式表示和实现
一、单链表的定义和表示
1、基本思想
用一组任意的存储单元(连续或离散)存储线性表的数据元素, 用每个数据元素所带的指针来确定其后继元素的存储位置
(1)数据元素的存储映像称作结点:(数据域,指针域)
(2)链式存储结构:线性表中的n个结点链接成一个链表
(3)每个结点只包含一个指针域的链表称为单链表,有两个指针域的链表称为双向链表,首尾相接的链表称为循环链表
2、单链表的存储结构
Typedef struct LNode
{ ElemType data; //结点的数据域
Struct LNode *next; //结点的指针域
} LNode, *LinkList; //LinkList 是指向LNode的指针类型
相当于:Typedef LNode *LinkList;
定义:LinkList L;(头指针L) // 或LNode *p;(结点指针p)
使用:L->next,或p->data等
3、带头结点的单链表示意
首元结点:存储第一个数据元素a1的结点
头结点:首元结点之前指向首元结点的附加结点
头指针:指向链表中第一个结点的指针
求单链表长度
[算法步骤]
(1)顺链扫描,同步计数
(2)注意初值和循环条件 的匹配
T(n)=O(n)
[算法描述]
//法1
int Length(LinkList L)
{ LNode *p=L->next;
int len=1;
while (p!=NULL)
{
p=p->next;
len++;
}
return len;
}
//法2
void Length(LinkList &L)
{
LNode *p=L;
int len=0;
while (p->next)
{
p=p->next;
len++;
}
L->data=len;
}
按位序取值
[算法步骤]
(1)指针p指向首元结点,计数器j初值为1
(2)p不为空,或未达到序号 i 时:顺链扫描,同步计数
(3)退出循环时,如果指针p为空,或者计数器j大于位序i,说明位序 i 值不合法,取值失败返回ERROR;否则取值成功j==i, p所指结点即为位序为 i 的结点
[算法描述]
Status GetElem(LinkList L, int i, ElemType &e)
{ LinkList p=L->next;
int j=1; // p指向首元结点,j为计数器
while (p&&j<i)
{
p=p->next;
++j;
}
if (!p || j>i)
return ERROR; // i>n或i≤0,不合法
e=p->data; // 取结点i的值
return OK;
}
T(n)=O(n)
按值查找返回指针
[算法步骤]
(1)指针p指向首元结点
(2)p不为空,且p所指结点值不等于给定值e,则循环执行: 顺链扫描
(3)退出循环时,返回指针p。查找成功时p为结点地址值,查 找失败时p为NULL
[算法描述]
LNode *LocateElem(LinkList L, ElemType e)
{ LinkList p=L->next ; // 带头结点的单链表
while (p&&p->data!=e)
p=p->next;
return p;
}
T(n)=O(n)
按值查找返回位序
[算法步骤]
(1)指针p指向首元结点,计数器 i 初值为1
(2)p不为空,且p所指结点值不等于给定值e,则循环执行: 顺链扫描,同步计数
(3)退出循环时,如果指针p为空则返回0,否则返回位序 i
[算法描述]
int FindElem(LinkList L, ElemType e)
{ LinkList p=L->next ;
int i=1;
while (p&&p->data!=e)
{
p=p->next;
i++;
}
if (!p)
return 0;
else
return i;
}
T(n)=O(n)
插入元素
[算法步骤]
s->next=p->next;
p->next=s;
注意:这里两条语句不能交换顺序,一旦交换了顺序,当先执行p->next=s后,ai+1就找不到了!
[算法描述]
Status ListInsert(LinkList &L,int i, ElemType e)
{ LinkList p=L,s;
int j=0;
while (p&&j<i-1)
{
p=p->next;
++j;
} //p指向第i-1个结点
if (!p||j>i-1)
return ERROR;// i>n+1或i<1
s=new LNode;
s->data=e;
s->next=p->next;
p->next=s;
return OK;
}
T(n)=O(n)
删除元素
[算法步骤]
q=p->next;
p->next=q->next;
delete q;
[算法描述]
Status ListDelete(LinkList &L, int i)
{
LinkList p=L,q;
int j=0;
while (p->next&&j<i-1)
{
p=p->next;
++j;
} // p->next是待删结点,p指向第i-1个结点
if (!(p->next)||j>i-1)
return ERROR; //i>n或i<1
q=p->next;
p->next=q->next;
delete q;
return OK;
}
T(n)=O(n)
创建单链表
1)前插法建表在链表表头插入新结点,结点次序与输入次序相反
2)后插法建表 将新结点插到链表尾部,增设一个尾指针 last,使其始终 指向当前链表的尾结点
前插法创建单链表
[算法步骤]
1)建立一个“空表”;
2)输入数据元素an,建立结点;
3)输入数据元素an-1,插入到an 之前…
4)依次类推,直至输入a1为止
[算法描述]
void CreateList_H(LinkList &L, int n)
{ LinkList p;
L=new LNode;
L->next=NULL;
for (int i=n; i>0; --i)
{
p=new LNode;
cin>>p->data;
p->next = L->next;
L->next=p;
}
}
T(n)=O(n)
后插法创建单链表
[算法步骤]
1)建立一个“空表”;
2)输入数据元素a1,建立结点;
3)输入数据元素a2,插入到a1 之后…
4)依次类推,直至输入an为止
[算法描述]
void CreateList_R(LinkList &L, int n)
{ LinkList p,last=L;
L=new LNode;
L->next=NULL;
for (i=0; i<n;++i)
{
p=new LNode;
cin>>p->data;
p->next=NULL;
last->next=p;
last=p;
}
}
T(n)=O(n)
三、循环链表
1、基本思想
循环链表是一种首尾相接的链表:最后一个结点的next指针 不为NULL,而是指向表头结点
2、与单链表的表示完全一致
Typedef struct LNode
{ ElemType data ;
Struct LNode *next ;
} LNode, *LinkList ;
3、与单链表的区别:体现在操作上
(1)循环条件的控制不同
单链表:p!=NULL; p->next!=NULL;
循环链表:p!=L; p->next!=L;
(2)遍历的起点不同
单链表:L
循环链表:L或p
合并两个单链表
p=La;
while (p->next)
p=p->next;// p指向最后一个结点
p->next = Lb->next;
delete Lb;
T(n)=O(La.length)
合并两个循环链表
p=La;
while (p->next!=La)
p=p->next;
p->next=Lb->next; // Lb连入La末尾
p=p->next;
while (p->next!=Lb)
p=p->next;
p->next=La; // 修改尾指针
delete Lb;
T(n) = O(La.length+Lb.length)
四、双向链表
1、双向链表是指在前驱和后继方向都能遍历的线性链表
双向链表通常采用带头结点的循环链表形式
2、双向(循环)链表的表示
Typedef struct DuLNode
{ ElemType data;
struct DuLNode *prior;
struct DuLNode *next;
} DuLNode, *DuLinkList;
值得注意的是:
p==p->prior->next ==p->next->prior
双向循环链表的插入
(1) s->next = p->next;
(2) p->next = s;
(3) s->next->prior = s;
(4) s->prior = p;
T(n)=O(n)
双向循环链表的删除
(1) p->next=p->next->next;
(2) p->next->prior=p;
[算法描述]
Status ListDele_Dul(DuLinkList &L,int i,ElemType &e)
{ DuLNode *p=L->next;
int j=1;
while (p&&j<i)
{
p=p->next;
j++;
} // p指向第i个结点
if (!p||j>i)
return ERROR;
e=p->data;
p->prior->next=p->next;
p->next->prior=p->prior;
delete p;
return OK;
}// ListDele_Dul
T(n)=O(n)
线性表的用法
一、线性表的合并
[算法步骤]
1)分别获取LA表长m和LB表长n
2)从LB中第一个元素开始,循环执行以下操作:
①从LB查找第i(1≤i≤n)个数据元素e
②在LA中查找元素e,如果不存在则将e插入到表LA的末尾
[算法描述]
void MergeList(List &LA,List LB)
{ //将所有在线性表LB中但不在LA中的数据元素插入到LA中
m=ListLength(LA);
n=ListLength(LB); //求线性表的长度
for(i=1;i<=n;i++)
{
GetElem(LB,i,e); //取LB中第i个数据元素赋给e
if(!LocateElem(LA,e))
ListInsert(LA,e);
}
}
二、有序表的合并
1、顺序有序表的合并
1)创建一个表长为m+n的空表
2)指针pc初始化,指向LC的第一个元素
3)指针pa和pb初始化,分别指向LA和LB的第一个元素
4)当指针pa和pb均未达到相应表尾时,依次比较pa和pb所指元素值,从LA或LB中摘取元素值较小的结点插入到LC表尾
5)如果pb到达LB表尾,依次将LA的剩余元素插入LC的最后
6)如果pa到达LA表尾,依次将LB的剩余元素插入LC的最后
void MergeList_Sq(SqList LA,SqList LB,SqList &LC)
{
int *pa=LA.elem,*pbLB.elem,*pc,*pa_last,*pb_last;
LC.length=LA.length+LB.length;
LC.elem=new ElemType[LC.length];
pc=LC.elem;
pa_last=LA.elem+LA.length-1;
pb_last=LB.elem+LB.length-1;
while(pa<=pa_last && pb<=pb_last)
{
if(*pa<=*pb)
*pc++=*pa++;
else
*pc++=*pb++;
}
while(pa<=pa_last)
*pc++=*pa++;
while(pb<=pb_last)
*pc++=*pb++;
}
2、链式有序表的合并
1)指针pa和pb初始化,分别指向LA和LB的第一个结点
2)LC的结点取值为LA的头结点
3)指针pc初始化,指向LC的头结点
4)当指针pa和pb均未达到相应表尾时,依次比较pa和pb所指 元素值,从LA或LB中摘取元素值较小的结点插入到LC表尾
5)将非空表的剩余结点插入LC的最后
6)释放LB的头结点
void MergeList_L(LinkList &LA,LinkList &LB,LinkList &LC)
{
LinkList pa=LA->next,pb=LB->next,pc;
LC=LA; pc=LC;
while(pa && pb)
{
if(pa->data <= pb->data)
{
pc->next=pa;
pc=pa;
pa=pa->next;
}
else
{
pc->next=pb;
pc=pb;
pb=pb->next;
}
}
pc->next=pa?pa:pb;
delete LB;
} //MergeList_L()