Loading

数据结构笔记②——线性表

数据结构

​ ♥made by Randq^_^


数据结构

第二章 线性表

2.1 线性表的定义及基本操作

线性表的定义

线性表是具有相同数据类型的n(n>=0)个数据元素有限序列,其中n为表长,当n=0时线性表示一个空表

若用L命名线性表,则其一般表示为L = (a1 , a2 , ... , ai , ai+1 , ... , an

ai是线性表中放入“第i个”元素 (位序从1开始,数组下标从0开始)

a1是表头元素,an是表尾元素

除了第一个元素,所有元素都有唯一前驱;除了最后一个元素,所有元素都有唯一后继

线性表的基本操作

Snipaste_2020-12-01_07-17-52

2.2 线性表的顺序表示

存储结构

顺序表——用顺序存储的方式实现线性表

$顺序存储:把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。$

实现方式

静态分配
//静态分配-使用“静态数组实现”
#define MAXSIZE 10
typedef struct {
	ElemType data[MAXSIZE];//用静态的“数组”存放数据元素,大小一旦确定就无法改变
	int length;
}SqList; //顺序表的类型定义——静态分配方式
//基本操作——初始化一个线性表
void InitList(SqList &L) {
	for(int i=0; i<MAXSIZE; i++){
		L.data[i] = 0;
	}
	L.length = 0;
} 
动态分配
//动态分配-使用“动态数组实现”
#define InitSize 10	//顺序表的初始长度 
typedef struct {
	int * data; //指示动态分配数组的指针	
	int MAXSIZE;	 //顺序表的最大容量 
	int length; 	 //顺序表的当前长度 
}SeqList; //顺序表的类型定义——动态分配方式
void InitList(SeqList &L) {
	L.data = (int *)malloc(InitSize*sizeof(int));
	L.length = 0;
	L.MAXSIZE = InitSize; 
} 
//增加动态数组的长度
void IncreaseSize(SeqList & L, int len) {
	int * p = L.data;
	L.data = (int *)malloc((MAXSIZE+len)*sizeof(int));
	for(int i=0; i<L.length; i++){
		L.data[i] = p[i];//将数据复制到新区域 
	}
	L.MAXSIZE = MAXSIZE+len;//顺序表最大长度增加 len 
	free(p);//释放原来的空间 
} 

特点

  1. 随机访问,可以在O(1)时间内找到第i个元素。
  2. 存储密度高,每个结点只储存数据元素。
  3. 拓展不方便(即使采用动态分配的方式实现,拓展长度的时间复杂度也较高)
  4. 插入,删除操作不方便,需要移动大量元素

基本操作

顺序表的插入
//基本操作
//插入
bool ListInsert (SqList &L, int i, int e) {//在L的位序i处插入元素e
	if( i<1 || i>length+1)	return false;//判断i的位置是否有效
	if(L.length>=MAXSIZE)	return false;//当前空间已满不能插入 
	for(int j=L.length; j>=i; j--) {
		L.data[j] = L.data[j-1];
	}
	L.data[i-1] = e;
	L.length++;
} //时间复杂度:最好O(1),最坏O(n),平均O(n)
顺序表的删除
//删除
bool ListDelete (SqList & L, int i, int & e) {
	if(i<1 || i>length)	return false; //判断i的位置是否合法
	e = L.data[i-1];
	for(int j=i; j<=L.length-1; j++) {
		L.data[j-1] = L.data[j];
	} 
	L.length--;
	return true;
}//时间复杂度:最好O(1),最坏O(n),平均O(n)
顺序表的查找
//查找 
    //按位查找
    ElemType GetElem (SqList L, int i) {
        return L.data[i-1];
    } //时间复杂度O(1) 
    //按值查找;在顺序表中查找第一个元素值等于e的元素,并返回其位序 
    int LocateElem (SqList L, ElemType e) {
        for(int i=0; i<L.length; i++){
            if(L.data[i]==e)//结构体的比较不能直接使用==,需要写一个比较函数 
                return i+1;
        }
        return 0;
    }//时间复杂度:最好O(1),最坏O(n),平均O(n)

2.3 线性表的链式表示

单链表的定义

单链表

用“链式存储”(存储结构)实现了“线性结构”(逻辑结构),一个结点存储一个数据元素, 各结点间的先后关系用一个指针来表示

注:代码中有些结点申请 没有考虑到没有申请到的情况

用代码定义一个单链表
typedef struct LNode {//定义单链表节点类型 
	ElemType data;//数据与 每个结点存放一个数据元素 
	struct LNode * next;//指针域 指针指向下一个结点 
}LNode, *LinkList;
LNode * p = (LNode *)malloc(sizeof(struct LNode));
//要表示一个单链表时,只需要声明一个头指针L,指向单链表的第一个结点
//	LNode * L;//声明一个指向单链表第一个结点的指针 
//	LinkList L; 
//	强调这是一个单链表  ——  使用LinkList
//	强调这是一个结点	——	使用LNode * 
单链表的两种实现

带头结点

//带头结点的单链表
//初始化一个单链表
bool InitList (LinkList & L) {
	L = (LNode *)malloc(sizeof(LNode));//分配一个头结点 
	if(L==NULL)//内存不足,分配失败 
		return false;
	L->next = NULL;//头结点之后暂时还没有结点 
	return true; 
}
//判断单链表是否为空
bool Empty (LinkList L) {
	return (L->next == NULL);
} 
void test(){
	LinkList L;//声明一个指向单链表的指针 
	InitList(L);//初始化一个指针 
	//...后续代码... 
} 

不带头结点

//不带头结点的单链表
//初始化一个单链表
bool InitList (LinkList & L) {
	L = NULL; 
	return true; 
}
//判断单链表是否为空
bool Empty (LinkList L) {
	return (L==NULL);	
} 
void test(){
	LinkList L;//声明一个指向单链表的指针 
	InitList(L);//初始化一个指针 
	//...后续代码... 
} 

单链表上基本操作的实现

单链表的插入
按位序插入
//在第i个位置插入元素e(带头结点) 
bool ListInsert (LinkList &L, int i, ElemType e) {
	if(i<1)	return false;
	LNode * p = GetElem(L, i-1); 
	return InsertNextNode(p, e);
} 
bool ListInsert (LinkList &L, int i, ElemType e) {
	if(i<1)	return false;
	LNode * p;//指针p是当前扫描到的结点 
	int j = 0;//当前p指向的是第几个结点 
	p = L;//L指向头结点,头结点是第0个结点(不存数据)
	while (p!=NULL && j<i-1) {//循环找到第i-1个结点 
		p = p->next;
		j++; 
	} 
	if(p==NULL)
		return false;
	LNode *s = (LNode *)malloc(sizeof(LNode));
	s->data = e;
	s->next = p->next;//这两句不可颠倒... 
	p->next = s;//将s结点连到p之后 
	return true; 
}
//在第i个位置插入元素e(不带头结点) 
bool ListInsert (LinkList &L, int i, ElemType e) {
	if(i<1)	return false;
	if(i==1) {//插入第一个结点的操作与其他结点操作不同 
		LNode *s = (LNode *)malloc(sizeof(LNode));
		s->data = e;
		s->next = L; 
		L = s;
		return true; 
	} 
	LNode * p;//指针p是当前扫描到的结点 
	int j = 1;//当前p指向的是第几个结点 
	p = L;//L指向第一个结点
	while (p!=NULL && j<i-1) {//循环找到第i-1个结点 
		p = p->next;
		j++; 
	} 
	return InsertNextNode(p, e); 
}

指定结点的后插操作

//后插操作:在p结点之后插入元素e 
bool InsertNextNode (LNode *p, ElemType e) {
	if(p==NULL)
		return false;
	LNode *s = (LNode *)malloc(sizeof(LNode));
	s->data = e;
	s->next = p->next; 
	p->next = s;//将s结点连到p之后 
	return true; 
} 

指定结点的前插操作

//前插操作:在p结点前插入元素 e
bool InsertPriorNode (LNode *p, ElemType e) {
	if(p==NULL) 
		return false;
	LNode *s = (LNode *)malloc(sizeof(LNode));
	s->next = p->next;
	p->next = s;//新结点s连到p后 
	s->data = p->data;//将p中元素复制到s中 
	p->data = e;//p中元素覆盖为e 
	return true;
} 
//前插操作:在p结点前插入结点s 
bool InsertPriorNode (LNode *p, LNode *s) {
	if(p==NULL || s==NULL) 
		return false;
	s->next = p->next;
	p->next = s;//新结点s连到p后 
	ElemType tmp = p->data;//交换数据域部分 
	p->data = s->data;
	s->data = tmp;
	return true;
} 
单链表的删除

按位序删除(带头结点)

//按位序删除(带头结点)
bool ListDelete (LinkList &L, int i, ElemType &e) {
	if(i<1)	return false;
	LNode *p;//指针p指向当前扫描到的结点 
	int j = 0;//当前p指向的是第几个结点 
	p = L;//指向头结点,头结点是第0个结点 
	while (p!=NULL || j<i-1) {//循环找到第i-1个结点 
		p = p->next;
		j++;
	} 
	if(p==NULL)
		return false;
	LNode *q = p->next;//q指向被删除结点
	e = q->data;//用e返回元素的值 
	p->next = q->next;//将*q从链中断开 
	free(q);//释放结点的存储空间 
	return true; //删除成功 
}

指定结点的删除(指定结点是最后一个节点时,需要特殊处理)

//指定结点的删除
bool DeleteNode (LNode *p) {
	if(p==NULL) return false;
	if(p->next != NULL){//如果p不是最后一个结点 
		LNode *q = p->next;//令q指向*p的后继结点 
		p->data = q->data;//和后继结点交换数据域 
		p->next = q->next;//将*q从链中断开 
		free(q);//释放后继结点的存储空间 
	} 
	else if(p->next == NULL){
		LNode *q = L;
		int j = 0;
		while (q->next->next!=NULL) {
			q = q->next;
			j++;
		}  
		free(p);
		q->next = NULL; 
	}
	return true;
} 
单链表的查找

按位查找

//单链表的查找
//按位查找:返回第i个元素(带头结点)
LNode * GetElem(LinkList L, int i) {
	if(i<0)	return false;
	LNode *p;//指针p指向当前扫描到的结点 
	int j = 0;//当前p指向的是第几个结点 
	p = L;//指向头结点,头结点是第0个结点 
	while (p!=NULL || j<i) {//循环找到第i个结点 
		p = p->next;
		j++;
	} 
	return p;
} 

按值查找

//按值查找
LNode * LocateElem (LinkList L, ElemType e) {
	LNode *p = L->next;
	while(p!=NULL && p->data != e){//这里假设data的值可以直接比较 
		p = p->next;
	}
	return p;//找到后返回该结点指针,否则返回NULL 
}

求单链表长度

int Length(LinkList L) {
	int len = 0;
	LNode *p = L;
	while(p->next!=NULL) {
		p = p->next;
		len++;
	}
	return len;
} 
单链表的建立

尾插法与头插法

//头插法建立单链表
//重要应用:链表的逆置
LinkList List_HeadInsert (LinkList & L) {//逆向建立单链表
	LNode * s, int x;
	L = (LNode *)malloc(sizeof(LNode));  //创建头结点
	L->next = NULL;//初始为空链表 
	scanf("%d", &x);//输入结点的值
	while(x!=9999) {//输入9999表示结束
		s = (LNode *)malloc(sizeof(LNode));//创建新结点 
     	if(s==NULL)
			return false;
		s->data = x;
		s->next = L->next;
		L->next = s;
		scanf("%d", &x);		
	} 
	return L;
} 
//尾插法建立单链表
LinkList List_TailInsert (LinkList & L) {
	int x;
	L = (LNode *)malloc(sizeof(LNode));  //创建头结点
 	LNode * s,* r = L;//r为表尾指针 
	L->next = NULL;//初始为空链表 
	scanf("%d", &x);//输入结点的值
	while(x!=9999) {//输入9999表示结束
		s = (LNode *)malloc(sizeof(LNode));//创建新结点 
		if(s==NULL)
			return false;
		s->data = x;
		r->next = s;//在r结点之后插入元素x 
		r = s; //永远保持r指向最后一个结点 
		scanf("%d", &x);		
	} 
	r->next = NULL;//尾结点指针置空 
	return L;
} 

双链表

双链表的初始化
//双链表:可进可退,存储密度稍低一点 
//双链表的初始化 
typedef struct DNode {
	ElemType data;
	struct DNode *prior,*next;
}DNode, *DLinkList;
bool InitDLinkList (DLinkList L) {
	L = (DNode *)malloc(sizeof(DNode));//分配一个头结点 
	if(L == NULL)//内存不足,分配失败 
		return false; 
	L->prior = NULL;//头结点的prior永远指向NULL 
	L->next = NULL;//头结点之后暂时还没有结点 
	return true;
}
bool Empty(DLinkList L) {c
	return (L->next == NULL);
}
双链表的插入
//双链表的插入——在p结点之后插入s结点
bool InsertNextDNode(DNode *p, DNode *s){
	if(p==NUll || s==NULL)//非法参数 
		return false; 
	s->next = p->next;
	if(p->next != NULL) //如果p后有后继结点 
		p->next->prior = s;
	s->prior = p;
	p->next = s
	return true;
}
双链表的删除
//双链表的删除
bool DeleteNextDNode(DNode *p){
	if(p==NULL)	return false;
	DNode *q = p->next;//找到p的后继q 
	if(q==NULL) return false;//p没有后继 
	p->next = q->next;
	if(q->next!=NULL) 
		q->next->prior = p;
	free(q);
	return true;
} 
void DestoryList (DLinkList &L) {
	//循环释放各个数据结点
	while (L->next!=NULL)
		DeleteNextDNode(L);
	free(L);//释放头结点
	L = NULL;//头指针指向NULL 
}
双链表的遍历
//双链表的遍历 
//后向遍历
while(p!=NULL) {
	p = p->next;
} 
//前向遍历
while(p!=NULL) {
	p = p->prior;
} 
//前向遍历(跳过头结点)
while(p->prior!=NULL) {
	p = p->prior;
} 

循环链表

循环单链表

单链表:表尾节点的next指针指向NULL ——从一个结点出发只能找到后续的各个结点

循环单链表:表尾的next指针指向头结点 ——从一个结点出发可以找到其他任何一个结点

从头部找到尾部 O(n),从尾部找到头部O(1),很多时候对链表的操作都是在头部或尾部,可以让R指向表尾因素(插入,删除时需要修改R)

//循环链表
//初始化一个循环单链表
bool InitList (LinkList L) {
	L = (LNode *)malloc(sizeof(LNode));//分配一个头结点 
	if(L == NULL)//内存不足,分配失败 
		return false; 
	L->next = L;//头结点的next指向头结点 
	return true;
} 
//判断循环单链表是否为空
bool Empty(LinkList L) {
	return (L->next == L);
} 
//判断结点p是否为循环单链表的表尾结点
bool isTail(LinkList L, LNode * p) {
	return (p->next == L);
}
循环双链表

双链表:表头结点的prior指向NULL;表尾结点的next指向NULL

循环双链表:表头结点的prior指向表尾结点;表尾结点的next指向表头结点

typedef struct DNode {
	ElemType data;
	struct DNode *prior,*next;
}DNode, *DLinkList;
//初始化空的循环双链表
bool InitDLinkList (DLinkList L) {
	L = (DNode *)malloc(sizeof(DNode));//分配一个头结点 
	if(L == NULL)//内存不足,分配失败 
		return false; 
	L->prior = L;//头结点的prior指向头结点
	L->next = L;//头结点的next指向头结点 
	return true;
}
//判断循环双链表是否为空
bool Empty(DLinkList L) {
	return (L->next == L);
}
//判断结点p是否为循环双链表的表尾节点 
bool isTail(DLinkList L, DNode * p) {
	return (p->next == L);
}
//在p结点之后插入s结点
bool InsertNextDNode(DNode *p, DNode *s){
	s->next = p->next;
	p->next->prior = s;
	s->prior = p;
	p->next = s;
	return true;
} 
//删除p结点的后继节点q 
bool DeleteNextDNode(DNode *p){
 	DNode *q = p->next;
	p->next = q->next;
	q->next->prior = p;
	free(q);
	return true;
} 

静态链表

静态链表:分配一整片连续的内存空间,各个结点集中安置

每个数据元素4B,每个游标4B(每个结点共8B)

设起始地址为addr,e1的存放地址为addr+8*2

初始化静态链表 把a[0].next = -1;其他结点的next = -2,表示结点空闲。
查找 从头结点出发挨个往后遍历节点
插入位序为i的结点 找到一个空的结点,存入数据元素->从头结点出发找到位序为i-1的结点->修改新结点的next->修改i-1号结点的next
删除某个结点 从头结点出发找到前驱结点->修改前驱结点的游标->被删除结点的游标设为-2

顺序表和链表的比较

逻辑结构:都属于线性表,都是线性结构

存储结构:顺序存储和链式存储

基本操作

基本操作 顺序表 链表
需要预分配大片连续空间,若分配空间过小,则之后不方便拓展容量;若分配空间过大,则浪费内存资源。 静态分配:静态数组(容量不可改变)动态分配:动态数组(容量可改变,但需要移动大量元素,时间代价高) 只需要分配一个头结点,方便拓展
修改length = 0 静态分配:静态数组(系统自动回收空间)动态分配:动态数组(需要手动free) 依次删除各个结点(free)
增删 插入、删除元素都要将后续元素都前移,后移;时间复杂度O(n),时间开销用于移动元素 插入、删除元素只需修改指针即可;时间复杂度O(n),时间开销用于查找目标元素。
按位查找:O(1) 按值查找:O(n),若表内元素有序,可在O(lgn)时间内找到 按位查找:O(n) 按值查找:O(n)

使用:当表长难以估计,需要增加/删除元素时 ——链表

​ 表长可预估,查询(搜索)操作较多 ——顺序表

posted @ 2020-12-18 23:42  randq  阅读(78)  评论(0)    收藏  举报