【数据结构二】线性表和基本操作

77 阅读10分钟

线性表

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

用L命名线性表,则其一般表现为: L= (a1,....aN) 概念:

  • ai: 第i个元素线性表中的位序
  • a1: 表头元素
  • aN:表尾元素
  • 除第一个元素外,每个元素有且仅有一个直接前驱,除最后一个元素外,每个元素有且仅有一个直接后继

图片.png

顺序表

定义:用顺序存储的方式实现线性表

优点: 可随机存取,存储密度高

缺点:要求大片连续空间,改变容量不方便

Tip: C语言中有一个 sizeOf(ElemType) 来知道一个数据元素的大小

ElemType: 是顺序表中存放的数据元素类型

图片.png

图片.png

typedef struct{
	int *data; // 指示动态分配数组的指针 
	int MaxSize; //顺序表的最大容量
	int length; //顺序表的当前长度 
}SqList; 
// 初始化顺序表 
void InitList(SqList &L){
	// 用malloc函数申请一片连续的存储空间
	L.data=(int *)malloc(InitSize*sizeof(int))
	L.length = 0;
	L.MaxSize = InitSize; 
}
//增加动态数组的长度
void IncreateSize(SqList &L, int len){
	int *p = L.data;
	L.data = (int *)malloc((L.MaxSize+len)*sizeof(int))
	for(int i = 0; i<L.length;i++){
		L.data[i] = p[i]; // 将数据复制到新区域(时间开销大) 
	}
	L.MaxSize = L.MaxSize+len;//顺序表最大长度增加len
	free(p); //释放原来的内存空间 
} 

int main(){
	SqList L; //声明一个顺序表 
	InitList(L); //初始化顺序表
	// 往顺序表中随便插入几个元素....
	// 动态增加数组的长度 
	IncreateSize(L,5);
	return 0; 
}

顺序表的插入 删除

图片.png

顺序表的基本操作

  • 按位查找:获得表L中的第i个位置的元素的值
  • 按值查找:在表L中查找具有关键字值地元素

图片.png

链表(链式存储)

单链表

定义:每个结点除了存放数据元素外,还要存储指向下一个结点的指针

优点:不要求大片连续空间,改变容量方便

缺点:不可随机存取,要耗费一定空间存放指针

图片.png

#include <iostream>

typedef struct LNode{//定义单链表结点类型 
	int data; //每个节点存放一个数据类型 
	struct LNode *next; //指针指向下一个节点 
}LNode,*LinkList;
//初始化一个空的单链表
bool InitList(LinkList &L){
	L=NULL; // 空表,暂时还没有任何结点
	return true; 
} 
//初始化一个单链表(带头结点)
bool InitHeadList(LinkList &L){
	L=(LNode *) malloc(sizeof(LNode));//分配一个头结点
	if(L==NULL)  //内存不足,分配失败  
		return false;
	L->next = NULL; //头结点之后暂时还没有节点
	return true; 
} 
//判断单链表是否为空
bool Empty(LinkList &L){
	return (L==NULL);
} 
void test(){
	LinkList L; //声明一个指向单链表的指针
	// 初始化一个空表
	InitList(L);
}

//头插法建立单链表
LinkList List_HeadInsert(LinkList &L){//创建头结点 
	LNode *s;
	int x;
	L=(LinkList)malloc(sizeof(LNode));//创建头部结点
	L->next=NULL; //初始为空链表
	scanf("%d",&x); //输入结点的值
	while(x!=9999){
		s=(LNode*)malloc(siezof(LNode));//创建新结点
		s->data=x;
		s->next=L->next;
		L->next=s; //将新结点插入表中,L为头指针
		scanf("%d",&x); 
	} 
	return L;
}


int main(int argc, char** argv) {
	return 0;
}

图片.png

#include <iostream>

typedef struct LNode{//定义单链表结点类型 
	int data; //每个节点存放一个数据类型 
	struct LNode *next; //指针指向下一个节点 
}LNode,*LinkList;
//初始化一个空的单链表
bool InitList(LinkList &L){
	L=NULL; // 空表,暂时还没有任何结点
	return true; 
} 
//初始化一个单链表(带头结点)
bool InitHeadList(LinkList &L){
	L=(LNode *) malloc(sizeof(LNode));//分配一个头结点
	if(L==NULL)  //内存不足,分配失败  
		return false;
	L->next = NULL; //头结点之后暂时还没有节点
	return true; 
} 
//判断单链表是否为空
bool Empty(LinkList &L){
	return (L==NULL);
} 
void test(){
	LinkList L; //声明一个指向单链表的指针
	// 初始化一个空表
	InitList(L);
}

//头插法建立单链表
LinkList List_HeadInsert(LinkList &L){//创建头结点 
	LNode *s;
	int x;
	L=(LinkList)malloc(sizeof(LNode));//创建头部结点
	L->next=NULL; //初始为空链表
	scanf("%d",&x); //输入结点的值
	while(x!=9999){
		s=(LNode*)malloc(siezof(LNode));//创建新结点
		s->data=x;
		s->next=L->next;
		L->next=s; //将新结点插入表中,L为头指针
		scanf("%d",&x); 
	} 
	return L;
}
// 在第i个位置插入元素e(带头结点)
bool ListInsert(LinkList &L,int i, int 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) //i值不合法
		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 ListInsertNohead(LinkList &L, int i, int e){
	if(i<1) return false;
	if(i == 1){//插入第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; //p指向第1个结点(注意:不是头结点)
	while(p != NULL && j < i-1){//循环找到第i-1个结点
		p = p->next;
		j++; 
	} 
	if(p==NULL) //i值不合法
		return false;
	LNode *s = (LNode *)malloc(sizeof(LNode));
	s->data = e;
	s->next = p->next;
	p->next = s;
	return true; // 插入成功 
} 
//后插操作,在p结点之后插入元素e(带头结点) 
bool InsertNextNode(LNode *p, int e){
	if(p==NULL) return false;
	LNode *s = (LNode *)malloc(sizeof(LNode));
	if(s==NULL) //内存分配失败
		return false;
	s->data = e; //用结点s保存数据元素e
	s->next = p->next;
	p->next = s; //将结点s连到p之后
	return true; 
} 
//前插操作,在p结点之前插入元素e(带头结点) 
bool InsertPreNode(LNode *p, int e){
	if(p == NULL) return false;
	LNode *s = (LNode *)malloc(sizeof(LNode));
	if(s == NULL) //内存分配失败
		return false;
	s->next = p->next;
	p->next = s; //新结点s连到p之后 
	s.data = p.data; //将p中元素复制到s中 
	p.data = e; //p中元素覆盖为e 
	return true; 
} 
//删除第i个元素的结点(带头结点) 
bool ListDelete(LinkList &L, int i, int &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) //i值不合法
		return false;
	if(p->next == NULL) //第i-1结点后已无其他结点
		return false;
	LNode *q = p->next; //令q指向被删除结点
	e = q->data;//用e返回元素的值
	p->next = q->next; //将*q结点从链中断开
	free(q); //释放结点的存储空间
	return true; //删除成功 
} 
//删除指定结点p,时间复制度O(1) 
bool DeleteNode(LNode *p){
	if(p == NULL) return false;
	LNode *q = p->next; //令q指向*p的后继结点 
	
	// 注意!如果那个结点刚好是最后一个结点,下面的操作就有bug
	// 然后就只能用整个链表来查询,依次查询p的前驱来删,时间复杂度O(n) 
	// 所以就可以体现出单链表的局限性:
	// 无法逆向检索,有时候不太方便 
	
	p->data = p->next->data; //和后继结点交换数据域
	p->next = q->next; //将*q结点从链中“断开”
	free(q); //释放后继结点的存储空间
	return true; 
} 

int main(int argc, char** argv) {
	return 0;
}

单链表的查找

  • 按位查找:获取表L中第i个位置的元素的值
  • 按值查找:在表L中查找具有给定关键字值的元素
//按位查找,返回第i个元素(带头结点)
LNode * GetElem(LinkList L, int i){
	if(i < 0) return NULL;
	LNode *p; //指针p指向当前扫描到的结点
	int j = 0; //当前p指向的是第i个结点
	p = L; //L指向头结点,头结点是第0个结点(不存数据)
	while(p != NULL && j<i){ //循环找到第i个结点
		p = p->next;
		j++; 
	} 
	return p;
} 
//按值查找,找到数据域==e 的结点
LNode * LocateElem(LinkList L,int e){
	LNode *p = L->next;
	// 从第一个结点开始查找数据域为e的结点
	while(p != NULL && p->data !=e){
		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_TailInsert(LinkList &L){//正向建立单链表 
	int x; //设ElemType为整形
	L = (LNode *)malloc(sizeof(LNode)); //建立头结点 
	L->next = NULL;
	LNode *s,*r=L; //r为表尾指针
	scanf("%d",&x);  //输入结点的值
	while(x!=9999){
		//输入9999表示结束
		s = (LNode *)malloc(sizeof(LNode));
		s->data = x;
		r->next=s;
		r=s; //r指向新的表尾结点
		scanf("%d",&x); 
	} 
	r->next = NULL;
	return L;
} 
//头插法建立单链表
LinkList List_HeadInsert(LinkList &L){
	//逆向建立单链表
	LNode *s;
	int x;
	L = (LinkList)malloc(sizeof(LNode)); //创建头结点
	L->next = NULL; //初始为空链表
	scanf("%d",&x); //输入结点的值
	while(x!=9999){ 
		s = (LNode*)malloc(sizeof(LNode)); //创建新结点
		s->data = x;
		s->next = L->next;
		L->next = s;  //将新结点插入表中,L为头指针
		scanf("%d",&x); 
	} 
	return L;
} 

双链表

图片.png

//定义一个双链表
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->prior = NULL; // 头结点的prior永远指向NULL 
	L->next = NULL; // 头结点之后暂时还没有节点 
	return true; 
} 
void test(){
	//初始化双链表
	DlinkList L;
	InitDLinkList(L);
} 
//判断双链表是否为空(带头结点)
bool Empty(DLinkList L){
	return(L->next == NUll)
} 
//在p结点之后插入s结点
bool InsertNextDNode(DNode *p, DNode *s){
	if(p==NULL || s==NULL) //非法参数
	    return false 
	s->next = p->next; //将结点s插入到结点*p之后 
	if(p->next != NULL) //如果p结点有后继结点 
		p->next->prior = s;
	s->prior = p;
	p->next = s;
} 
// 删除p结点后的后继结点
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不是最后一个结点
		p->next->prior = p;
	free(q);//释放结点空间 
} 
//销毁一个双链表
void DestoryList(DLinkList &L){
	//循环释放各个数据结点
	while(L->next != NULL){
		DeleteNextDNode(L)
	} 
	free(L); //释放头结点
	L=NULL; //头指针指向NULL; 
} 

循环单链表

定义:最后一个结点指向头结点

  • 从一个结点出发可以找到其他任何一个结点
typedef struct LNode{
	int data;
	struct LNode *next;
}LNode, *LinkList;
//初始化一个循环单链表
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)
} 

循环双链表

定义:表头结点的prior指向表尾结点,表尾结点的next指向头结点

图片.png

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->prior = L; //头结点的prior指向头结点 
	L->next = L; //头结点的next指向头结点 
	return true; 
} 
// 在p结点之后插入s结点
bool InsertNextDNode(DNode *p, DNode *s){
	s->next=p->next;
	p->next->prior = s;
	p->next = s;
	s->prior = p;
} 

静态链表

定义:分配一整片连续的内存空间,各个结点集中安置

优点:增删 操作不需要大量移动元素

缺点:不能随机存取,只能从头结点开始依次往后查找,容量固定不可变

图片.png

//-------------静态链表
#define MaxSize 10
typedef struct Node{ //静态链表的最大长度
	int data; //存储数据元素
	int next; //下一个元素的数组下标 
}SLinkList[MaxSize]; 
void test(){
	SLinkList a;
}