派大汤的数据结构笔记---线性表

数据结构笔记

  • 二、线性表
    • 1. 线性表的定义和基本操作
      • 1.1 基本概念和术语
      • 1.2 线性表的基本操作
    • 2.线性表的顺序表示
      • 2.1 顺序表的定义
      • 2.2 顺序表的实现
        • 2.2.1. 静态顺序表
          • 结构体定义及初始化
          • 插入操作
          • 删除操作
          • 按位查找
          • 按值查找
          • 判空、输出、求长操作
        • 2.2.2 动态顺序表
    • 3.线性表的链式表示
      • 3.1 单链表的定义
      • 3.2 单链表的实现
        • 3.1.1 带头结点的单链表
          • 结构体定义及初始化
          • 头插法建立单链表
          • 尾插法建立单链表
          • 按序号查找结点值
          • 按值查找结点值
          • 按位置插入结点
          • 结点前插结点
          • 按位序删除结点
          • 按结点删除结点
          • 求表长、打印链表
        • 3.1.2 不带头结点的单链表
          • 结构体定义及初始化
      • 3.3 双链表及其操作
        • 基本概念
        • 结构体及初始化
        • 在结点前插入结点
        • 在结点后删除结点
      • 3.4 循环表及其操作
        • 3.4.1 循环单链表
        • 3.4.2 循环双链表
      • 3.5 静态链表
    • 4. 顺序表VS链表
      • 顺序表与链表的比较
      • 顺序表与链表的选用

二、线性表

1. 线性表的定义和基本操作

1.1 基本概念和术语

派大汤的数据结构笔记---线性表_第1张图片
线性表:线性表是具有相同数据类型的n(n>=0)个数据元素的有限序列,n为表长。若用L命名线性表,则表示为:( a 1 a_1 a1, a 2 a_2 a2, a 3 a_3 a3 a i a_i ai, a i + 1 a_{i+1} ai+1…, a n a_n an)。

理解:
相同数据类型:每个元素所占的空间一样大。
有限序列:表中的元素一定有限个。例如:所有整数组成的序列不是线性表。

几个概念:
表头元素: a 1 a_1 a1,第一个数据元素
表尾元素: a n a_n an,最后一个数据元素
空表:n=0的线性表

线性表的逻辑特性(线性结构的特性):

  1. 存在唯一的一个被称作“第一个”的数据元素;
  2. 存在唯一的一个被称作“最后一个”的数据元素;
  3. 除第一个之外的数据元素均只有一个前驱;
  4. 除最后一个之外的数据元素均只有一个后继。

线性表的特性:

  1. 表中的元素个数有限。
  2. 表中元素有逻辑上的顺序性,表中元素有其先后次序。
  3. 表中元素都是数据元素,每个元素都是单个元素。
  4. 表中元素的数据类型都相同,每个元素占用着相同大小的空间。

1.2 线性表的基本操作

  • InitList(&L); 初始化表,构造一个空的线性表。
  • Length(L); 求表长,返回线性表L的长度,即L中元素的个数。
  • LocateElem(L,e);按值查找操作,在L中查找值为e的元素。
  • GetElem(L,i); 按位查找操作,获取L中第i个元素的值。
  • ListInsert(&L,i,e);插入操作,在L中第i个位置上插入指定元素e。
  • ListDelete(&L,i,&e);删除操作。删除L中第i个位置的元素,用e返回该元素的值。
  • PrintList(L);输出操作,按照前后顺序输出线性表L的所有元素值。
  • Empty(L);判空操作,若L为空表,返回true,否则false。
  • DestroyList(&L);销毁操作,销毁线性表,释放线性表,释放L所占用的内存空间。

2.线性表的顺序表示

2.1 顺序表的定义

顺序表:线性表的顺序存储。用一组地址连续的存储单元依次存储线性表中的数据元素,是的逻辑上相邻的两个元素在物理位置上也相邻。

顺序表的特点:

  1. 主要特点:以能够通过首地址和元素序号在时间O(1)内找到指定元素,实现随机存取。
  2. 顺序表存储密度高,每个结点只存数据元素。
  3. 顺序表在逻辑上相邻,在物理上也相邻,所以插入和删除操作都需要移动大量元素。

派大汤的数据结构笔记---线性表_第2张图片

2.2 顺序表的实现

2.2.1. 静态顺序表

静态分配:

  • 使用“静态数组”实现。
  • 大小一旦确定无法改变,空间占满后,再加入新的数据会产生溢出。
  • 给各个数据元素分配连续的存储空间,大小为MaxSize*sizeof(ElementType)。
结构体定义及初始化
#include 
#define MaxSize 10

typedef struct {
	int data[MaxSize];
	int length;
}ContiguousList;

/*
  *Function:InitList
  *Input:
		arg1:ContiguousList &cl 要初始化的顺序表
*/
void InitList(ContiguousList &cl){
	for (int i=0;i<MaxSize;i++){
		cl.data[i]=0;
	}
	cl.length=0;
}

插入操作

基本步骤:
1.判断插入位置是否合法,不合法返回false,插入失败。
2.将顺序表中的第i个元素及以后的所有元素向后移动一个位置,cl.data[j]=cl.data[j-1]
3.腾出空位置插入新元素e,cl.data[i-1]=e。
4.线性表长度+1,length++。

派大汤的数据结构笔记---线性表_第3张图片派大汤的数据结构笔记---线性表_第4张图片派大汤的数据结构笔记---线性表_第5张图片派大汤的数据结构笔记---线性表_第6张图片派大汤的数据结构笔记---线性表_第7张图片

/*
  *Function:ListInsert
  *Input:
		arg1:ContiguousList &cl 要插入的元素的顺序表
		arg2:int i 要插入元素在顺序表中的位置
		arg3:int e 要插入元素的值
*/
bool ListInsert(ContiguousList &cl,int i,int e){
	if(i<1||i>cl.length+1){
		printf("The scope of I is invalid");
		return false;
	}
	if(cl.length>MaxSize){
		printf("ContiguousList is full");
		return false;
	}
	for (int j=cl.length;j>=i;j--){
		cl.data[j]=cl.data[j-1];
	}
	cl.data[i-1]=e;
	cl.length++;
	return true;
}

时间复杂度分析:
最好情况:在表尾插入(i=n+1),元素后移语句不执行,时间复杂度为O(1)。
最坏情况:在表头插入(i=1),元素后移语句将执行n次,时间复杂度为O(n)。
平均情况:
i的范围:i=1,2,3…length+1;
每个位置上插入结点的概率为: P i P_i Pi= 1 n + 1 {1} \over {n+1} n+11;

i=1 循环执行n次
i=2 循环执行n-1次
i=3 循环执行n-2次

i=n+1 循环执行0次

所以移动结点的平均次数为:
∑ i = 1 n + 1 P i ( n − i + 1 ) \sum_{i=1}^{n+1}{P_i}(n-i+1) i=1n+1Pi(ni+1)= ∑ i = 1 n + 1 1 n + 1 ( n − i + 1 ) \sum_{i=1}^{n+1}{{1} \over {n+1}}(n-i+1) i=1n+1n+11(ni+1)= 1 n + 1 {1} \over {n+1} n+11 ∑ i = 1 n + 1 ( n − i + 1 ) \sum_{i=1}^{n+1}(n-i+1) i=1n+1(ni+1)= 1 n + 1 {1} \over {n+1} n+11 n ( n + 1 ) 2 {n(n+1)}\over{2} 2n(n+1)= n 2 {n}\over{2} 2n

删除操作

基本步骤:
1.判断所删除元素位置i是否合法,不合法返回false,删除失败。
2.将被删除元素的值赋给e,e=cl.data[i]。
3.将i位置之后的元素向前移,cl.data[j-1]=cl.data[j]。
4.线性表长度-1,length–。
派大汤的数据结构笔记---线性表_第8张图片派大汤的数据结构笔记---线性表_第9张图片 派大汤的数据结构笔记---线性表_第10张图片

/*
  *Function:ListDelete
  *Input:
		arg1:ContiguousList &cl 要删除的元素的顺序表
		arg2:int i 要删除元素在顺序表中的位置
		arg3:int &e 返回要删除元素的值
*/
bool ListDelete(ContiguousList &cl,int i,int &e){
	if (i<1||i>cl.length){
		printf("The scope of I is invalid");
		return false;
	}
	e=cl.data[i-1];
	for (int j=i;j<cl.length;j++){
		cl.data[j-1]=cl.data[j];
	}
	cl.length--;
	return true;
}

时间复杂度分析:
最好情况:删除表尾元素(i=n),元素后移语句不执行,时间复杂度为O(1)。
最坏情况:删除表头元素(i=1),元素后移语句将执行n-1次,时间复杂度为O(n)。
平均情况:
i的范围:i=1,2,3…length;
每个结点被删除的概率为: P i P_i Pi= 1 n {1} \over {n} n1;

i=1 循环执行n-1次
i=2 循环执行n-2次
i=3 循环执行n-3次

i=n 循环执行0次

所以移动结点的平均次数为:
∑ i = 1 n P i ( n − i ) \sum_{i=1}^{n}{P_i}(n-i) i=1nPi(ni)= ∑ i = 1 n 1 n ( n − i ) \sum_{i=1}^{n}{{1} \over {n}}(n-i) i=1nn1(ni)= 1 n {1} \over {n} n1 ∑ i = 1 n ( n − i ) \sum_{i=1}^{n}(n-i) i=1n(ni)= 1 n {1} \over {n} n1 n ( n − 1 ) 2 {n(n-1)}\over{2} 2n(n1)= n − 1 2 {n-1}\over{2} 2n1

按位查找
/*
  *Function:getElemByLocation
  *Input:
		arg1:ContiguousList &cl 要查找元素的顺序表
		arg2:int i 要查找元素在顺序表中的位置
*/
int getElemByLocation(ContiguousList cl,int i){
	return cl.data[i-1];
}

时间复杂度分析:
顺序表的各个元素在内存中连续存放,因此可以根据起始地址和数据元素大小直接找到第i个元素,所以时间复杂度为O(1)。

按值查找

派大汤的数据结构笔记---线性表_第11张图片派大汤的数据结构笔记---线性表_第12张图片派大汤的数据结构笔记---线性表_第13张图片

/*
	*Function:getElemByValue
	*Input:
		arg1:ContiguousList &cl 要查找元素的顺序表
		arg2:int e 要查找元素的值
	*Return: 返回查找值在顺序表中的次序,返回0则查找失败。
*/
int getElemByValue(ContiguousList cl,int e){
	for (int i=0;i<cl.length;i++){
		if (cl.data[i]==e){
			return i+1;
		}
	}
	return 0;
}

时间复杂度分析:
最好情况:查找表头元素,仅比较一次,时间复杂度为O(1)。
最坏情况:查找表尾元素(或者不存在),需要比较n次,时间复杂度为O(n)。
平均情况:
i的范围:i=1,2,3…length;
每个元素被查找的概率为: P i P_i Pi= 1 n {1} \over {n} n1;

i=1 循环执行1次
i=2 循环执行2次
i=3 循环执行3次

i=n 循环执行n次

所以移动结点的平均次数为:
∑ i = 1 n P i ∗ i \sum_{i=1}^{n}{P_i}*i i=1nPii= ∑ i = 1 n 1 n ∗ i \sum_{i=1}^{n}{{1} \over {n}}*i i=1nn1i= 1 n {1} \over {n} n1 n ( n + 1 ) 2 {n(n+1)}\over{2} 2n(n+1)= n + 1 2 {n+1}\over{2} 2n+1

判空、输出、求长操作
/*
	*Function:getLength
	*Input:
		arg1:ContiguousList &cl 要求取长度的顺序表
	*Return:顺序表长度
*/
int getLength(ContiguousList &cl){
	return cl.length;
}

/*
	*Function:PrintList
	*Input:
		arg1:ContiguousList cl 要打印的顺序表
*/
void PrintList(ContiguousList cl){
	for (int i=0;i<cl.length;i++){
		printf("第%d个元素为:%d\n",i+1,cl.data[i]);
	}
}

/*
	*Function:isEmpty
	*Input:
		arg1:ContiguousList cl 要判断的顺序表
	*Return:若表为空返回true,否则返回false
*/
bool isEmpty(ContiguousList cl){
	if (cl.length==0)
	{
		return true;
	}else{
		return false;
	}
}

2.2.2 动态顺序表

动态分配:

  • 使用“动态数组实现”;
  • L.data=(EleType *)malloc (sizeof(ElemType)*size);
  • 顺序表存满,可用malloc动态拓展顺序表的最大容量;
  • 需要将数据元素复制到新的存储区域,并用free函数释放原区域;

派大汤的数据结构笔记---线性表_第14张图片-派大汤的数据结构笔记---线性表_第15张图片

增加动态数组的过程

#include 
#include 
#define InitSize 5 //默认最大长度

typedef struct {
	int *data;	//指示动态分配数组的指针
	int MaxSize;
	int length;
}ContiguousList;

//初始化
void InitList(ContiguousList &cl){
	cl.data=(int *)malloc(InitSize*sizeof(int));
	if(!cl.data)
		exit(-1);
	cl.length=0;
	cl.MaxSize=InitSize;
}

/*
	*Function:IncreaseSize
	*Input:
		arg1:ContiguousList &cl 要动态分配空间的顺序表
		arg2:int len 要动态增加的长度
*/
void IncreaseSize(ContiguousList &cl,int len){
	int *p=cl.data;
	cl.data=(int *)malloc((cl.MaxSize+len)*sizeof(int));
	for (int i=0;i<cl.length;i++){
		cl.data[i]=p[i];
	}
	cl.MaxSize=cl.MaxSize+len;
	free(p);
}

/*
	*Function:ListInsert
	*Input:
		arg1:ContiguousList &cl 要插入的元素的顺序表
		arg2:int i 要插入元素在顺序表中的位置
		arg3:int e 要插入元素的值
*/
bool ListInsert(ContiguousList &cl,int i,int e){
	
	if(i<1||i>cl.length+1){
		printf("The scope of I is invalid");
		return false;
	}

	if(cl.length>=cl.MaxSize){
		IncreaseSize(cl,10);
	}

	for (int j=cl.length;j>=i;j--){
		cl.data[j]=cl.data[j-1];
	}
	cl.data[i-1]=e;
	cl.length++;
	return true;
}

/*
	*Function:getElemByValue
	*Input:
		arg1:ContiguousList cl 要查找元素的顺序表
		arg2:int e 要查找元素的值
	*Return: 返回查找值在顺序表中的次序,返回0则查找失败。
*/
int getElemByValue(ContiguousList cl,int e){
	for (int i=0;i<cl.length;i++){
		if (cl.data[i]==e){
			return i+1;
		}
	}
	return 0;
}

/*
	*Function:getElemByLocation
 	*Input:
		arg1:ContiguousList cl 要查找元素的顺序表
		arg2:int i 要查找元素在顺序表中的位置
	*Return: 返回该数值在顺序表中的序号
*/
int getElemByLocation(ContiguousList cl,int i){
	if (i<1||i>cl.length){
		printf("The scope of I is invalid");
		return false;
	}
	return cl.data[i-1];
}

/*
	*Function:getElemByLocation
 	*Input:
		arg1:ContiguousList cl 要删除元素的顺序表
		arg2:int i 要删除元素在顺序表中的位置
		arg3:int e 要删除元素值
*/
bool ListDelete(ContiguousList &cl,int i,int &e){

	if (i<1||i>cl.length){
		printf("The scope of I is invalid");
		return false;
	}
	e=cl.data[i-1];
	for (int j=i;j<cl.length;j++){
		cl.data[j-1]=cl.data[j];
	}
	cl.length--;
	return true;
}

/*
	*Function:PrintList
	*Input:
		arg1:ContiguousList cl 要打印的顺序表
*/

void PrintList(ContiguousList cl){
	
	for (int i=0;i<cl.length;i++){
		printf("第%d个元素为:%d\n",i+1,cl.data[i]);
	}
}

/*
	*Function:getLength
	*Input:
		arg1:ContiguousList cl 要求取长度的顺序表
*/
int getLength(ContiguousList cl){
	return cl.length;
}

/*
	*Function:isEmpty
	*Input:
		arg1:ContiguousList cl 要判断的顺序表
	*Return:若表为空返回true,否则返回false
*/
bool isEmpty(ContiguousList cl){
	if (cl.length==0)
	{
		return true;
	}else{
		return false;
	}
}

/*
	*Function:DestroyList
	*Input:
		arg1:ContiguousList cl 要销毁的顺序表
*/
bool DestroyList(ContiguousList &cl){
	if(cl.data){
		free(cl.data);
		cl.data=NULL;
		cl.length=0;
		cl.MaxSize=0; 
	}else{
		return false;
	}
	return true;
}

与静态顺序表相比,动态顺序表的操作相差并不大,不再赘述,只是多了动态分配空间的IncreaseSize()函数和Destroy()函数。

3.线性表的链式表示

3.1 单链表的定义

单链表:线性表的链式存储,通过任意一组存储单元存储线性表中的数据元素。
单链表的结点结构:
在这里插入图片描述

  • data域:数据域,存放自身的信息。
  • next域:指针域,存放其后继结点的地址。

派大汤的数据结构笔记---线性表_第16张图片

单链表的特点:

  • 优点:不要求大片连续空间,改变容量方便。
  • 缺点:不可随机存取,要消耗一定空间存放指针。

头结点:为了操作方便,在第一个结点之前附加结点,这个结点不存放任何数据。

头指针:用于标识一个单链表。
如果单链表没有头结点,那么头指针指向第一个结点。
如果单链表有头结点, 那么头指针指向头结点。

特别地,当头指针为null时,表示一个空表。

引入头结点的优点:
1.由于第一个数据结点的位置被存放在头结点的指针域,所以在链表的第一个位置上的操作和在表的其他位置一致,无需进行特殊操作。
2.无论链表是否为空,头指针都指向头结点的非空指针,空表与非空表的处理得到了同一。

3.2 单链表的实现

3.1.1 带头结点的单链表

结构体定义及初始化

基本步骤:
1.分配一个头结点,data域不存放任何数据。
2.让头指针L指向这个头结点。
3.因为是空表,让头结点指向null。

派大汤的数据结构笔记---线性表_第17张图片派大汤的数据结构笔记---线性表_第18张图片派大汤的数据结构笔记---线性表_第19张图片派大汤的数据结构笔记---线性表_第20张图片

typedef struct LNode{
	int data;
	LNode *next;
}LNode,*LinkList;

/*
	*Function:InitList
	*Input:
		arg1:LinkList &L 要初始化的链表
*/
bool InitList(LinkList &L){
	L=(LNode *)malloc(sizeof(LNode));
	if (L==NULL){
		return false;
	}
	L->next=NULL;
	return true;
}
头插法建立单链表

基本步骤:
1.设置指针s。LNode *s;
2链表L指向NULL。L->next=NULL;
3.给指针s分配空间。s=(LNode *)malloc (sizeof(LNode));
4.让s的data域为插入结点值。s->data=data;
5.s的next域指向L的next。s->next=L->next;
6.L的next指向s。L->next=s;
派大汤的数据结构笔记---线性表_第21张图片

/*
	*Function:List_HeadInsert
	*Description:头插法建立单链表
	*Input:
		arg1:LinkList &L 要建立的单链表
		arg2:int length 建立单链表的长度
*/
LinkList List_HeadInsert(LinkList &L,int length){
	int data;
	L=(LinkList)malloc(sizeof(LNode));
	LNode *s;
	L->next=NULL;
	while(length>0){
		printf("请输入第%d个结点的值:",length);
		scanf("%d",&data);
		s=(LNode *)malloc (sizeof(LNode));
		s->data=data;
		s->next=L->next;
		L->next=s;
		length--;
	}
	return L;
}

时间复杂度分析:插入一个结点,循环一次,插入n个结点循环n次。所以T(n)=O(n);

尾插法建立单链表

基本步骤:
1.分配指针s、r,s指向插入头结点,r用于指向表尾。LNode *s,*r=L;
2.给s分配空间。s=(LNode *)malloc (sizeof(LNode));
3.s的值为插入结点值。s->data=data;
4.指针r指向s。r->next=s;
5.指针r指向表尾。r=s;
派大汤的数据结构笔记---线性表_第22张图片派大汤的数据结构笔记---线性表_第23张图片派大汤的数据结构笔记---线性表_第24张图片派大汤的数据结构笔记---线性表_第25张图片

/*
	*Function:List_TailInsert
	*Description:尾插法建立单链表
	*Input:
		arg1:LinkList &L 要建立的单链表
		arg2:int length 建立单链表的长度
*/
LinkList List_TailInsert(LinkList &L,int length){
	int data;
	int i=0;
	L=(LinkList)malloc(sizeof(LNode));
	LNode *s,*r=L;	//指向头结点
	while(i<length){
		printf("请输入第%d个结点的值:",i+1);
		scanf("%d",&data);
		s=(LNode *)malloc (sizeof(LNode));
		s->data=data;
		r->next=s;
		r=s;
		i++;
	}
	r->next=NULL;
	return L;
}

时间复杂度分析:插入一个结点,循环一次,插入n个结点循环n次。所以T(n)=O(n);

按序号查找结点值

基本步骤:
1.设置指针p并指向第一个结点。LNode *p=L->next;
2.向后依次遍历。p=p->next;

/*
	*Function:GetElemByLocation
	*Description:按位查找
	*Input:
		arg1:LinkList &L 要查找的链表
		arg2:int i 要查找元素的位置
	*Return:
		若i无效,返回NULL;
		若i为0,返回头结点;
*/
LNode* GetElemByLocation(LinkList L,int i){
	int j=1;
	LNode *p=L->next;
	if (i==0){
		return L;
	}
	if (i<1){
		return NULL;
	}
	while (p&&j<i){
		p=p->next;
		j++;
	}
	printf("第%d个结点的值为:%d\n",i,p->data);
	return p;
}

时间复杂度分析:
每个元素被查找的概率为: P i P_i Pi= 1 n {1} \over {n} n1;

i=1 循环执行0次
i=2 循环执行1次
i=3 循环执行2次

i=n 循环执行n-1次

所以结点平均查找概率为:
∑ i = 1 n P i ∗ i \sum_{i=1}^{n}{P_i}*i i=1nPii= ∑ i = 1 n 1 n ∗ ( i − 1 ) \sum_{i=1}^{n}{{1} \over {n}}*{(i-1)} i=1nn1(i1)= 1 n {1} \over {n} n1 n ( n − 1 ) 2 {n(n-1)}\over{2} 2n(n1)= n − 1 2 {n-1}\over{2} 2n1

所以T(n)=O(n)

按值查找结点值

基本步骤:
1.设置指针p并指向链表L的第一个结点。
2.由后往前比较结点值。

/*
	*Function:GetElemByValue
	*Description:按值查找
	*Input:
		arg1:LinkList &L 要查找的链表
		arg2:int e 要查找元素的值
	*Return:
		若查找不到,返回NULL;
*/
LNode* GetElemByValue(LinkList L,int e){
	int location=1;
	LNode *p=L->next;
	while (p!=NULL&&p->data!=e){
		p=p->next;
		location++;
	}
	printf("值为%d的结点的序号为:%d\n",e,location);
	return p;
}

时间复杂度:T(n)=O(n)

按位置插入结点

基本步骤:
1.设置p指针用于指向i-1位置的结点。 LNode *p;
2.利用函数GetElemByLocation找到第i-1个结点。p=GetElemByLocation(L,i-1);
3.初始化s,使s的data域为插入元素值。s->data=e;
4.s的next域指向i-1的next域(即p的next域,成为第i个结点)。s->next=p->next;
5.p的next指向s。再次形成链表。p->next=s;

派大汤的数据结构笔记---线性表_第26张图片
派大汤的数据结构笔记---线性表_第27张图片
派大汤的数据结构笔记---线性表_第28张图片
派大汤的数据结构笔记---线性表_第29张图片

/*
	*Function:ListInsertRearNode
	*Description:在i位置插入结点
	*Input:
		arg1:LinkList &L 要插入的链表
		arg2:int i 要插入的位置
		arg3:int e 要插入的元素值
*/
bool ListInsertByLocation(LinkList &L,int i,int e){
	if (i<1){
		return false;
	}
	LNode *p;
	p=GetElemByLocation(L,i-1);
	if(p==NULL){
		return false;
	}
	LNode *s=(LNode *)malloc(sizeof(LNode));
	s->data=e;
	s->next=p->next;
	p->next=s;
	return true;
}

时间复杂度:T(n)=O(n)
时间开销主要花费在p=GetElemByLocation(L,i-1)上,如果在给定结点后直接插入,则时间复杂度为O(1)。

结点前插结点

1.初始化s,将s插入到p前面。s->next=p->next; p->next=s;
2.交换数据部分。s->data=p->data;p->data=e;

派大汤的数据结构笔记---线性表_第30张图片派大汤的数据结构笔记---线性表_第31张图片派大汤的数据结构笔记---线性表_第32张图片派大汤的数据结构笔记---线性表_第33张图片

/*
	*Function:ListInsertRearNode
	*Description:将某个结点进行前插操作
	*Input:
		arg1:LinkList &L 要插入的链表
		arg2:LNode *s 要插入的结点
		arg3:LNode *p 将s插入到p前面
*/
bool ListInsertRearNode(LinkList &L,LNode *s,LNode *p){
	s->next=p->next;
	p->next=s;
	int temp=p->data;
	p->data=s->data;
	s->data=temp;
	return true;
}
按位序删除结点

基本步骤:
1.通过结点p找到第i-1个结点的位置。p=GetElemByLocation(L,i-1);
2.分配结点q,他是第i-1个结点的后继结点。LNode *q=p->next;
3.取值e保留删除结点值。e=q->data;
4.使得p结点跳过q结点指向q的后继结点。p->next=q->next;
5.释放q结点。free(q);
派大汤的数据结构笔记---线性表_第34张图片派大汤的数据结构笔记---线性表_第35张图片派大汤的数据结构笔记---线性表_第36张图片派大汤的数据结构笔记---线性表_第37张图片派大汤的数据结构笔记---线性表_第38张图片

/*
	*Function:ListDeleteByLocation
	*Description:按照位序删除结点
	*Input:
		arg1:LinkList &L 要删除结点的链表
		arg2:int i 要删除的位置
		arg3:int e 要删除的元素值
	*Return:
		int e :删除的元素值
		若成功,返回true。失败返回false。
*/
bool ListDeleteByLocation(LinkList &L,int i,int &e){
	if (i<1){
		return false;
	}
	LNode *p;
	int j=0;
	p=GetElemByLocation(L,i-1);
	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;
}

时间复杂度分析:
每个元素被删除的概率为: P i P_i Pi= 1 n {1} \over {n} n1;

i=1 循环执行0次
i=2 循环执行1次
i=3 循环执行2次

i=n 循环执行n-1次

所以结点平均删除概率为:
∑ i = 1 n P i ∗ i \sum_{i=1}^{n}{P_i}*i i=1nPii= ∑ i = 1 n 1 n ∗ ( i − 1 ) \sum_{i=1}^{n}{{1} \over {n}}*{(i-1)} i=1nn1(i1)= 1 n {1} \over {n} n1 n ( n − 1 ) 2 {n(n-1)}\over{2} 2n(n1)= n − 1 2 {n-1}\over{2} 2n1

所以T(n)=O(n)

按结点删除结点

基本步骤:
1.分配结点q为被删结点p的后继结点。LNode *q=p->next;
2.使得p的值为其后继结点的值。p->data=p->next->data;
3.使得p指向其后面的后面(把原本的p结点跳过)。p->next=q->next;
4.释放q结点。free(q);

派大汤的数据结构笔记---线性表_第39张图片派大汤的数据结构笔记---线性表_第40张图片派大汤的数据结构笔记---线性表_第41张图片派大汤的数据结构笔记---线性表_第42张图片

/*
	*Function:ListDeleteByNode
	*Description:按照结点删除结点
	*Input:
		arg1:LNode &p 要删除的结点
	*Return:
		若成功,返回true。失败返回false。
*/
bool ListDeleteByNode(LNode *p){
	if(p==NULL){
		return false;
	}
	LNode *q=p->next;
	p->data=p->next->data;
	p->next=q->next;
	free(q);
	return true;
}

时间复杂度:
传入结点,无需遍历,所以时间复杂度T(n)=O(1)。

注意:如果传入的结点为表尾结点,则p->next->data会报空指针异常,所以此代码不能删除表尾结点,若要删除需要从头开始遍历。

由此我们可以看出单链表的缺点:无法逆向检索

求表长、打印链表
/*
	*Function:getLength
	*Description:求表长
	*Input:
		arg1:LinkList L
	*Return:
		返回表长
*/
int getLength(LinkList L){
	int length=0;
	LNode *p=L->next;
	while (p!=NULL)
	{
		p=p->next;
		length++;
	}
	return length;
}

/*
	*Function:ListPrint
	*Description:打印链表
	*Input:
		arg1:LinkList &L 要打印的链表
*/
void PrintList(LinkList L){
	LNode *p=L;
	p=p->next;
	int i=1;
	while(p->data!=NULL){
		printf("第%d个结点的值为:%d\n",i,p->data);
		p=p->next;
		i++;
	}

}

3.1.2 不带头结点的单链表

结构体定义及初始化

派大汤的数据结构笔记---线性表_第43张图片派大汤的数据结构笔记---线性表_第44张图片派大汤的数据结构笔记---线性表_第45张图片

typedef struct LNode{
	int data;
	LNode *next;
}LNode,*LinkList;

bool InitList(LinkList &L){
	L=NULL;
	return true;
}

3.3 双链表及其操作

基本概念

双链表:在单链表的基础上每个指针附设一个结点,指向前面的结点。
在这里插入图片描述
特点

  1. 在进行修改过程中要保证不断链,相较于单链表操作稍微麻烦。
  2. 双链表可以可方便的找到其前驱结点,插入删除复杂度仅为O(1)。

双链表为空:L->prior=L;L->next=L;

结构体及初始化

派大汤的数据结构笔记---线性表_第46张图片

typedef struct DNode{
	int data;
	struct DNode *prior,*next;
}DNode,*DLinkList;

bool InitDLinkList(DLinkList &L){
	L=(DNode *)malloc(sizeof (DNode));
	if(L==NULL){
		return false;
	}
	L->next=NULL;
	L->prior=NULL;
	return true;
}

在结点前插入结点

基本步骤:
1.s结点指向p结点的后继结点。s->next=p->next;
2.判断是否为表尾结点,若果不是还需要让p结点的前驱结点指向s。p->next->prior=s;
3.s的前驱结点指向p。s->prior=p;
4.p的后继结点指向s。p->next=s;

派大汤的数据结构笔记---线性表_第47张图片派大汤的数据结构笔记---线性表_第48张图片派大汤的数据结构笔记---线性表_第49张图片派大汤的数据结构笔记---线性表_第50张图片

/*
	*Function:InsertNextDNode
	*Description:在p结点之后插入s结点
	*Input:
		arg1:DNode *p 
		arg2:DNode *s 插入的结点
*/
bool InsertNextDNode(DNode *p,DNode *s){

	if(p==NULL && s==NULL){
		return false;
	}

	s->next=p->next;
	if(p->next!=NULL){
		p->next->prior=s;
	}

	s->prior=p;
	p->next=s;
	return true;

}

时间复杂度:T(n)=O(1)

在结点后删除结点

基本步骤:
1.找到p结点的后继结点。DNode *q=p->next;
2.判断是否有后继结点,没有则无法删除。return false;
3.p结点指向q的后继结点。p->next=q->next;
4.q结点是不是最后一个结点,如果不是连接q的前驱结点指向p。q->next->prior=p;
5.释放q结点。free(q);

派大汤的数据结构笔记---线性表_第51张图片派大汤的数据结构笔记---线性表_第52张图片派大汤的数据结构笔记---线性表_第53张图片派大汤的数据结构笔记---线性表_第54张图片

/*
	*Function:DeleteNextDNode
	*Description:删除p结点的后继结点
	*Input:
		arg1:DNode *p 
*/
bool DeleteNextDNode(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;

}

时间复杂度:T(n)=O(1)

3.4 循环表及其操作

3.4.1 循环单链表

循环单链表:表中最后一个结点的指针不是NULL,而是改为指向头结点而形成的一个环。

派大汤的数据结构笔记---线性表_第55张图片

特点:
  • 从一个结点出发可以找到其他任意一个结点。
  • 在任意位置操作是等价的,不需判断是否是表头表尾。

3.4.2 循环双链表

循环双链表:
1.表头结点的prior指针指向表尾结点。
2.表尾结点的next指向头结点。

3.5 静态链表

静态链表:分配一整片连续的空间,各个结点集中安排位置。
派大汤的数据结构笔记---线性表_第56张图片

特点:
  • 固定的连续空间。
  • 指针是结点的相对地址,又称游标。
  • 插入删除只需要移动指针,不需要移动元素。
    • 不能随机存取,只能从前往后查。

4. 顺序表VS链表

顺序表与链表的比较

从逻辑结构来看:二者都属于线性表,都是线性结构。

从存储结构来看:

顺序表

  • 优点:支持随机存取,存储密度高。
  • 缺点:大片连续空间分配不方便,改变容量不方便。

链表

  • 优点:离散的小空间分配方便,改变容量方便。
  • 缺点:不可随机存取,存储密度低。

从基本操作来看:

1.初始化操作:

  • 顺序表的静态分配容量不可更改,动态分配容量虽然能够更改但是需要移动大量的元素,时间代价高。
  • 链表只需分配头指针或者头结点,容量扩展方便。

2.增加、删除操作:

  • 顺序表插入删除都需要将元素向后移动,时间复杂度为O(n),来自于移动元素,若是数据量很大,移动代价高。
  • 链表操作插入删除只需要修改指针即可,时间复杂度为O(n),来自于查找元素,相同数据量的情况下,查找代价低。

3.查找操作:

按位查找:
顺序表:O(1)
链表:O(n)

按值查找:
顺序表:O(n)
链表:O(n)

顺序表与链表的选用

1.基于存储的考虑:当难以估计线性表的长度或者存储规模时,适宜采用链表。
2.基于运算的考虑:
若是经常按照序号查找数据元素,适宜采用顺序表。
若是经常进行插入删除操作,适宜采用链表。

你可能感兴趣的:(基础学习,数据结构)