【数据结构(二)】线性表

文章目录

二. 线性表

1 线性表的定义

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


2 顺序表

2.1 静态分配

#include<stdio.h>							//定义表的最大长度

#define MaxSize 10    

typedef struct{
	int data[MaxSize];						//用静态的"数组"存放数据元素
	int length; 							//顺序表的当前长度  
}SqList;       								//顺序表的类型定义(静态分配方式) 		 

void InitList(SqList &L){
	 for(int i=0;i<MaxSize;i++){
	 	L.data[i]=0;  						//将所有数据元素设置为默认初始值  
	 }
	 L.length=0;
}

int main(){
	SqList L;								//声明一个顺序表
	InitList(L);							//初始化一个顺序表
	for(int i=0;i<MaxSize;i++){
		printf("data[%d]=%d\n",i,L.data[i]);
	}
	return 0; 
}

2.2 动态分配

#include<stdio.h>
#include<stdlib.h>							//malloc、free函数的头文件 

#define InitSize 10 						//默认的最大长度

typedef struct{
	int  *data;								//指示动态分配数组的指针
	int MaxSize; 							//顺序表的最大容量
	int length;  							//顺序表的当前长度 
}SeqList; 


void InitList(SeqList &L){							//初始化								
	L.data =(int*)malloc(InitSize*sizeof(int)) ;	//用malloc 函数申请一片连续的存储空间
	L.length=0;
	L.MaxSize=InitSize;
} 

void IncreaseSize(SeqList &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(void){
	SeqList L; 										//声明一个顺序表
	InitList(L);									//初始化顺序表
	IncreaseSize(L,5);
	return 0; 
}

2.3 顺序表的特点

  1. 随机访问 ,可以在O(1)时间内找到第i个元素。
  2. 存储密度高,每个节点只存储数据元素
  3. 拓展容量不方便
  4. 插入、删除操作不方便,需要移动大量元素

2.4 顺序表的基本操作

2.4.1 插入操作 :平均时间复杂度O(n)
bool ListInsert(SqList &L, int i, int e){ 
	if(i<1||i>L.length+1) 						//判断i的范围是否有效
		return false;
		
	if(L.length>=MaxSize) 						//当前存储空间已满,不能插入  
		  return false;
	
	for(int j=L.length; j>=i; j--){    			//将第i个元素及其之后的元素后移
		  L.data[j]=L.data[j-1];
	}
	
	L.data[i-1]=e;  							//在位置i处放入e
	L.length++;      							//长度加1
	
	return true;
}

2.4.2 删除操作:平均时间复杂度O(n)
bool LisDelete(SqList &L, int i, int &e){ 				// e用引用型参数 												
    if(i<1||i>L.length) 								//判断i的范围是否有效
    return false;

    e = L.data[i-1]    									//将被删除的元素赋值给e

    for(int j=i; j<L.length; j++){    					//将第i个后的元素前移
        L.data[j-1]=L.data[j];
    }
    
    L.length--;      									//长度减1
    
    return true;
}

2.4.3 按位查找(获取L表中第i个位置的值):平均时间复杂度O(1)
#define MaxSize 10            	//定义最大长度 

typedef struct{
    ElemType data[MaxSize];  	//用静态的“数组”存放数据元素 
    int Length;              	//顺序表的当前长度
}SqList;                    	 //顺序表的类型定义

ElemType GetElem(SqList L, int i){
    if(i<1||i>L.Length)			// ...判断i的值是否合法
    return -1;
    
    return L.data[i-1];      	//注意是i-1
}

2.4.4 按值查找:平均时间复杂度O(n)
#define InitSize 10            		 	//定义最大长度 

typedef struct{
    ElemTyp data[InitSize];;  			//用静态的“数组”存放数据元素 
    int Length;            		  		//顺序表的当前长度
}SqList;   
										
int LocateElem(SqList L, ElemType e){	//在顺序表L中查找第一个元素值等于e的元素,并返回其位序
    for(int i=0; i<L.lengthl i++)
        if(L.data[i] == e)  
            return i+1;    			 	//数组下标为i的元素值等于e,返回其位序i+1
    return 0;               			//推出循环,说明查找失败
}

3 链表

3.1 单链表

线性表的链式存储又称单链表,它是指通过一组任意的存储单元来存储线性表中的数据元素。

typedef struct LNode{			//定义单链表结点类型
    ElemType data; 				//数据域
    struct LNode *next;			//指针域
}LNode, *LinkList;

3.1.1 单链表的两种实现方式

头结点:代表链表上头指针指向的第一个结点,不带有任何数据。

  1. 带头结点的单链表
  2. 不带头结点的单链表

不带头结点:写代码麻烦!对第一个数据节点和后续数据节点的处理需要用不同的代码逻辑,对空表和非空表的处理也需要用不同的代码逻辑; 头指针指向的结点用于存放实际数据;
带头结点:头指针指向的头结点不存放实际数据,头结点指向的下一个结点才存放实际数据;

3.1.1.1 带头结点的单链表
typedef struct LNode{
    ElemType data;
    struct LNode *next;
}LNode, *LinkList;

											
bool InitList(LinkList &L){  				//初始化一个单链表(带头结点)
    L = (LNode*) malloc(sizeof(LNode));  	//头指针指向的结点——分配一个头结点(不存储数据)
    if (L == NULL)          				//内存不足,分配失败
        return false;
    L -> next = NULL;       				//头结点之后暂时还没有结点
    return true;
}

void test(){
    LinkList L;  							//声明一个指向单链表的指针: 头指针
    		
    InitList(L);							//初始化一个空表
    //...
}

bool Empty(LinkList L){						//判断单链表是否为空(带头结点)
    if (L->next == NULL)
        return true;
    else
        return false;
}


3.1.1.2 不带头结点的单链表
typedef struct LNode{
    ElemType data;
    struct LNode *next;
}LNode, *LinkList;


bool InitList(LinkList &L){  				//初始化一个空的单链表
    L = NULL; 								//空表,暂时还没有任何结点;
    return true;
}

void test(){
    LinkList L;  							//声明一个指向单链表的指针: 头指针
    
    InitList(L);							//初始化一个空表
    //...
}

bool Empty(LinkList L){						//判断单链表是否为空
    if (L == NULL)
        return true;
    else
        return false;
}

3.1.2 单链表的插入、删除
3.1.2.1 按位序插入(带头结点)O(n)

ListInsert(&L, i, e)在表L中的第i个位置上插入指定元素e,找到第i-1个结点(前驱结点),将新结点插入其后;其中头结点可以看作第0个结点,故i=1时也适用。

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


bool ListInsert(LinkList &L, int i, ElemType e){  	//在第i个位置插入元素e(带头结点)    
    if(i<1)											//判断i的合法性, i是位序号(从1开始)
        return False;
    
    LNode *p;       								//指针p指向当前扫描到的结点 
    int j=0;       									//当前p指向的是第几个结点
    p = L;         									//L指向头结点,头结点是第0个结点(不存数据)

    												//循环找到第i-1个结点
    while(p!=NULL && j<i-1){     					//如果i>lengh, p最后会等于NULL
        p = p->next;             					//p指向下一个结点
        j++;
    }

    if (p==NULL)                 					//i值不合法
        return false;
    
    												//在第i-1个结点后插入新结点
    LNode *s = (LNode *)malloc(sizeof(LNode)); 		//申请一个结点
    s->data = e;
    s->next = p->next;
    p->next = s;                 					//将结点s连到p后,后两步千万不能颠倒qwq

    return true;
}

3.1.2.2 按位序插入(不带头结点)O(n)

ListInsert(&L, i, e)在表L中的第i个位置上插入指定元素e,找到第i-1个结点(前驱结点),将新结点插入其后; 因为不带头结点,所以不存在“第0个”结点,因此!i=1 时,需要特殊处理——插入(删除)第1个元素时,需要更改头指针L。

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

bool ListInsert(LinkList &L, int i, ElemType e){
    if(i<1)
        return false;
    
    
    if(i==1){											 //插入到第1个位置时的操作有所不同!
        LNode *s = (LNode *)malloc(size of(LNode));
        s->data =e;
        s->next =L;
        L=s;          									 //头指针指向新结点
        return true;
    }

    													//i>1的情况与带头结点一样!唯一区别是j的初始值为1
    LNode *p;       									//指针p指向当前扫描到的结点 
    int j=1;        									//当前p指向的是第几个结点
    p = L;          									//L指向头结点,头结点是第0个结点(不存数据)

    													//循环找到第i-1个结点
    while(p!=NULL && j<i-1){     						//如果i>lengh, p最后会等于NULL
        p = p->next;             						//p指向下一个结点
        j++;
    }

    if (p==NULL)                 						//i值不合法
        return false;
    
    													//在第i-1个结点后插入新结点
    LNode *s = (LNode *)malloc(sizeof(LNode)); 			//申请一个结点
    s->data = e;
    s->next = p->next;
    p->next = s;          
    return true;
}

3.1.2.3 指定结点的后插操作O(1)

InsertNextNode(LNode *p, ElemType e) 给定一个结点p,在其之后插入元素e; 根据单链表的链接指针只能往后查找,故给定一个结点p,那么p之后的结点我们都可知,但是p结点之前的结点无法得知。

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

bool InsertNextNode(LNode *p, ElemType 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;
}                         			

bool ListInsert(LinkList &L, int i, ElemType e){  //有了后插操作,那么在第i个位置上插入指定元素e的代码可以改成
    if(i<1)
        return False;
    
    LNode *p;       							//指针p指向当前扫描到的结点 
    int j=0;        							//当前p指向的是第几个结点
    p = L;          							//L指向头结点,头结点是第0个结点(不存数据)

    											//循环找到第i-1个结点
    while(p!=NULL && j<i-1){     				//如果i>lengh, p最后4鸟会等于NULL
        p = p->next;             				//p指向下一个结点
        j++;
    }

    return InsertNextNode(p, e)
}

3.1.2.4 指定结点的前插操作 O(1)

设待插入结点是s,将s插入到p的前面。我们仍然可以将s插入到*p的后面。然后将p->data与s->data交换,这样既能满足了逻辑关系,又能是的时间复杂度为O(1)。

bool InsertPriorNode(LNode *p, LNode *s){
    if(p==NULL || S==NULL)
        return false;
    
    s->next = p->next;
    p->next = s;  ///s连接到p
    ELemType temp = p->data;  //交换数据域部分
    p->data = s->data;
    s->data = temp;

    return true;
}

3.1.2.5 按位序删除节点(带头结点)O(n)

ListDelete(&L, i, &e) 删除操作,删除表L中第i个位置的元素,并用e返回删除元素的值;头结点视为“第0个”结点;思路:找到第i-1个结点,将其指针指向第i+1个结点,并释放第i个结点;

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

bool ListDelete(LinkList &L, int i, ElenType &e){
    if(i<1) return false;

    LNode *p;       								//指针p指向当前扫描到的结点 
    int j=0;        								//当前p指向的是第几个结点
    p = L;          								//L指向头结点,头结点是第0个结点(不存数据)

    												//循环找到第i-1个结点
    while(p!=NULL && j<i-1){     					//如果i>lengh, p最后会等于NULL
        p = p->next;             					//p指向下一个结点
        j++;
    }

    if(p==NULL) 
        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;
}

3.1.2.6 指定结点的删除O(1)

把需要删除结点的下一个结点数据复制给删除结点,再把下一个结点删除。

bool DeleteNode(LNode *p){
    if(p==NULL)
        return false;
    
    LNode *q = p->next;      			//令q指向*p的后继结点
    p->data = p->next->data; 			//让p和后继结点交换数据域
    p->next = q->next;       			//将*q结点从链中“断开”
    free(q);
    return true;
} 

3.1.3 单链表的基本操作
3.1.3.1 单链表的查找O(n)
3.1.3.1.1 按位查找

GetElem(L, i)按位查找操作,获取表L中第i个位置的元素的值;

LNode * GetElem(LinkList L, int i){
    if(i<0) return NULL;
    
    LNode *p;               	//指针p指向当前扫描到的结点
    int j=0;                	//当前p指向的是第几个结点
    p = L;                  	//L指向头结点,头结点是第0个结点(不存数据)
    while(p!=NULL && j<i){  	//循环找到第i个结点
        p = p->next;
        j++;
    }

    return p;               	//返回p指针指向的值
}

3.1.3.1.2 按值查找
LNode * LocateElem(LinkList L, ElemType e){
    LNode *P = L->next;    						//p指向第一个结点
    
    while(p!=NULL && p->data != e){				//从第一个结点开始查找数据域为e的结点
        p = p->next;
    }
    return p;           						//找到后返回该结点指针,否则返回NULL
}

3.1.3.2 单链表的长度 O(n)

Length(LinkList L):计算单链表中数据结点(不含头结点)的个数,需要从第一个结点看是顺序依次访问表中的每个结点。

int Length(LinkList L){
    int len=0;       			//统计表长
    LNode *p = L;
    while(p->next != NULL){
        p = p->next;
        len++;
    }
    return len;
}

3.1.3.3 单链表的创建
3.1.3.3.1 头插法建立单链表O(n)

每次都将生成的结点插入到链表的表头。

LinkList List_HeadInsert(LinkList &L){       //逆向建立单链表
    LNode *s;
    int x;
    L = (LinkList)malloc(sizeof(LNode));     //建立头结点
    L->next = NULL;                          //初始为空链表,这步不能少!

    scanf("%d", &x);                         //输入要插入的结点的值
    while(x!=9999){                          //输入9999表结束
        s = (LNode *)malloc(sizeof(LNode));  //创建新结点
        s->data = x;
        s->next = L->next;
        L->next = s;                         //将新结点插入表中,L为头指针
        scanf("%d", &x);   
    }
    return L;
}

3.1.3.3.2 尾插法建立单链表O(n)

每次将新节点插入到当前链表的表尾,所以必须增加一个尾指针r,使其始终指向当前链表的尾结点。好处:生成的链表中结点的次序和输入数据的顺序会一致。

LinkList List_TailInsert(LinkList &L){       	//正向建立单链表
    int x;                                   	//设ElemType为整型int
    L = (LinkList)malloc(sizeof(LNode));     	//建立头结点(初始化空表)
    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;
}

3.1.3.4 单链表的逆置

逆置链表初始为空,原表中结点从原链表中依次“删除”,再逐个插入逆置链表的表头(即“头插”到逆置链表中),使它成为逆置链表的“新”的第一个结点,如此循环,直至原链表为空;

LNode *Inverse(LNode *L)
{
	LNode *p, *q;
	p = L->next;     			//p指针指向第一个结点
	L->next = NULL;  			//头结点指向NULL

	while (p != NULL){
		q = p;
		p = p->next;
		q->next = L->next;  
		L->next = q;
	}
	return L;

3.2 双链表

双向链表是一种复杂类型的链表,它的节点包含指向序列中前一个节点和下一个节点的指针。

typedef struct DNode{            //定义双链表结点类型
    ElemType data;               //数据域
    struct DNode *prior;		 //前驱指针
    struct DNode *next;  		 //后继指针
}DNode, *DLinklist;

3.2.1 双链表的初始化(带头结点)
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;
}

void testDLinkList(){
    										//初始化双链表
    DLinklist L;         					// 定义指向头结点的指针L
    InitDLinkList(L);    					//申请一片空间用于存放头结点,指针L指向这个头结点
    //...
}

3.2.2 双链表是否为空
bool Empty(DLinklist L){					//判断双链表是否为空
    if(L->next == NULL)    					//判断头结点的next指针是否为空
        return true;
    else
        return false;
}

3.2.3 双链表的插入操作

后插操作,InsertNextDNode(p, s) 在p结点后插入s结点

bool InsertNextDNode(DNode *p, DNode *s){ 	//将结点 *s 插入到结点 *p之后
    if(p==NULL || s==NULL) 					//非法参数
        return false;
    
    s->next = p->next;
    if (p->next != NULL)   					//p不是最后一个结点=p有后继结点  
        p->next->prior = s;
    s->prior = p;
    p->next = s;
    
    return true;
}

3.2.4 双链表的删除操作

删除p节点的后继节点

bool DeletNextDNode(DNode *p){		//删除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结点不是最后一个结点
        q->next->prior=p;
    free(q);

    return true;
}

3.2.5 双链表的销毁
bool DestoryList(DLinklist &L){		//销毁一个双链表							
    while(L->next != NULL){			//循环释放各个数据结点
        DeletNextDNode(L);  		//删除头结点的后继结点
    free(L); 						//释放头结点
    L=NULL;  						//头指针指向NULL

    }
}

3.2.6 双链表的遍历操作
3.2.6.1 前向遍历
while(p!=NULL){
    //对结点p做相应处理,eg打印
    p = p->prior;
}
3.2.6.2 后向遍历
while(p!=NULL){
    //对结点p做相应处理,eg打印
    p = p->next;
}

3.3 循环链表

3.3.1 循环单链表

最后一个结点的指针不是NULL,而是指向头结点

typedef struct LNode{            
    ElemType data;               
    struct LNode *next;  
}DNode, *Linklist;

3.3.1.1 循环单链表初始化
bool InitList(LinkList &L){					//初始化一个循环单链表
    L = (LNode *)malloc(sizeof(LNode)); 	//分配一个头结点
    if(L==NULL)             				//内存不足,分配失败
        return false;
    L->next = L;            				//头结点next指针指向头结点
    return true;
}

3.3.1.2 循环单链表是否为空
//判断循环单链表是否为空(终止条件为p或p->next是否等于头指针)
bool Empty(LinkList L){
    if(L->next == L)
        return true;    //为空
    else
        return false;
}

3.3.1.3 循环单链表判断结点是否为尾结点
//判断结点p是否为循环单链表的表尾结点
bool isTail(LinkList L, LNode *p){
    if(p->next == L)
        return true;
    else
        return false;
}

3.3.1.4 循环单链表和单链表的区别

循环单链表的优点:从表中任一节点出发均可找到表中其他结点。

  • 单链表: 从一个结点出发只能找到该结点后续的各个结点;对链表的操作大多都在头部或者尾部;设立头指针,从头结点找到尾部的时间复杂度=O(n),即对表尾进行操作需要O(n)的时间复杂度。
  • 循环单链表:从一个结点出发,可以找到其他任何一个结点;设立尾指针,从尾部找到头部的时间复杂度为O(1),即对表头和表尾进行操作都只需要O(1)的时间复杂度。

3.3.2 循环双链表

表头结点的 prior 指向表尾结点,表尾结点的 next 指向头结点

typedef struct DNode{          
    ElemType data;               
    struct DNode *prior, *next;  
}DNode, *DLinklist;
3.3.2.1 循环双链表初始化

//初始化空的循环双链表
bool InitDLinkList(DLinklist &L){
    L = (DNode *) malloc(sizeof(DNode));    	//分配一个头结点
    if(L==NULL)            						//内存不足,分配失败
        return false;  
    L->prior = L;          						//头结点的prior指向头结点
    L->next = L;           						//头结点的next指向头结点
}

3.3.2.2 循环双链表是否为空
//判断循环双链表是否为空
bool Empty(DLinklist L){
    if(L->next == L)
        return true;
    else
        return false;
}

3.3.2.3 循环双链表判断结点是否为尾结点
//判断结点p是否为循环双链表的表尾结点
bool isTail(DLinklist L, DNode *p){
    if(p->next == L)
        return true;
    else
        return false;
}

3.3.2.4 循环双链表的插入
void InsertNextDNode(DNode *p, DNode *s){ 
	if(p == NULL && s == NULL){
		return false;
	}
    s->next = p->next;
    p->next->prior = s;
    s->prior = p;
    p->next = s;
    return true;
}

3.3.2.5 循环双链表的删除

删除结点的后继结点

bool DeleteNextDNode(DNode *p){ 
	
    DNode *q = p->next;
    p->next = q->next;
    q->next->prior = p;
    free(q);
    return true;
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Eiker_3169

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值