数据结构学习笔记-线性表

线性表

主要内容
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=0return 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 0else 
 		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=0while (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=0while (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=NULLfor (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=NULLfor (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()
发布了2 篇原创文章 · 获赞 0 · 访问量 35

猜你喜欢

转载自blog.csdn.net/weixin_44043952/article/details/104237525
今日推荐