2.线性表

线性表

  1. 线性表的定义

    A[1]---A[2]---A[3]---A[4]---A[5]

    线性表是具有相同类型的n(n>=0)个元素的有限序列,其中n为表长,n=0时线性表是一个空表。若用L命名线性表,其表示形式为:
    L=(a1,a2,...,a[i],a[i+1],...,a[n]
    a[1]可以是int,也可以是 struct A(结构体

    序列:有次序,有限制

    特点:出第一个元素外,每个元素有且仅有一个直接前去;除最后一个外,每个元素都有且只有一个直接后继

    位序:位序从1开始,数组下表从0开始

  2. 基本操作

    InitList(&L):初始化表,构造一个空的线性表L,分配内存空间

    DestroyList(&L):销毁操作。销毁线性表,并释放线性表L所占用的内存空间

    ListInsert(&L,i,e):插入操作。在表L中的第i个位置插入指定元素e

    ListDelete(&L,i,e):删除操作。删除表L中第i个位置上的元素,并用e返回删除元素的值

    LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素

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

  3. 其他常用操作

    Length(L):求表长。返回线性表L的长度,即L中数据元素的个数

    PrintList(L):输出操作。按前后顺序输出线性表L的所有元素值

    Empty(L):判空操作。L为空返回true,否则返回false

  4. 其他建议

    实际开发中,可以根据实际需求定义其他基本操作
    函数名、参数的形式、命名都可以改变
    什么时候引用“&"——对参数的修改结果要带回来

    为什么要实现对数据库的基本操作?
    团队合作编程,定义的数据结构要让别人方便使用(封装)
    把常用操作封装成函数,避免重复工作,降低出错风险

注:数据的三要素,逻辑结构, 数据的运算,存储结构
定义——逻辑结构
基本操作——运算

顺序表(顺序存储)

  1. 定义(如何用代码实现)

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

    把逻辑上相邻的元素存储在物理上也相邻的存储单元中,元素之间的关系由存储单元关系的邻接关系来实现

    注:每个元素所占空间一样大

    设线性表第一个元素的存放位置是LOC(L)

    LOC--->a[1]

    LOC+数据元素的大小--->a[2]

    LOC+数据元素的大小*2--->a[3]

    ...

    LOC+数据元素的大小*(n-1)--->a[n]

    如何知道一个数据元素的大小?

    C语言 sizeof(Elemtype)函数

    EG:
    sizeof(int)=4B;

    typedef struct{
    	int num;			//号数
    	int people;		//人数
    }Customer;
    
     sizeof(Customer)=8B;
    
  2. 顺序表的实现方式——静态分配

    #define Maxsize 10			//定义最大长度
    typedef struct{
    	ElemType data[MaxSize];	//用静态的“数组”存放元素数据
    	int length;				//顺序表当前长度
    }Sqlist;					//顺序表的类型定义(静态分配方式)
    

    给各个元素分配连续的存储空间,大小为MaxSize*sizeof(ElemType)

    #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.lenth=0;	//此步骤不能省略,不然系统遗留的仓数据会使length失效
    }
    

    如果数组满了怎么办?
    顺序表的表长刚开始确定之后就无法更改,建议放弃治疗(存储空间是静态的)

    思考:如果一开始就声明很大的空间呢?
    会导致严重的内存浪费,降低内存利用率

  3. 顺序表的实现方式——动态分配

    key:动态申请和释放内存空间
    C—— malloc、free函数

    malloc函数
    L.data=(ElemType)malloc(sizeof(ElemType)InitSize);

    #include <stdlib.h>
    #define InitSIze 10;	//默认最大长度
    typedef struct{
    	Elemtype *data;		//指示动态分配数组的指针
    	int MaxSize;		//顺序的最大容量
    	int length;			//顺序表的当前最大长度
    }SeqList;
    
    //用malloc申请一片连续的存储空间
    void InitList(SeqList &L){
    	L.data=(int *)malloc(IntiSize*sizeof(int));
    	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;i<L.length;i++){
    		L.data[i]=p[i];			//复制数据到新区域,但是时间开销大
    	}
    	L.MaxSize=L.MaxSize+len;	//更新最大长度
    	free(p);					//释放原来内容
    }
    
    int main(){
    	SeqList L;
    	InitList(L);
    	//...插入元素
    	IncreaseSize(L,5);
    	return 0;
    }
    
  4. 顺序表的特点

    随机访问,可以在O(1)时间内找到第i个元素
    代码实现:data[i-1]
    静态分配、动态分配都一样

    存储密度高,每个节点只存储数据元素,链式存储需要存储指针

    扩展容量不方便(即便采用动态方式,扩展长度的时间复杂度也比较高)

    插入、删除操作不方便,需要移动大量元素

  5. 插入数据

    ListInsert(&L,i,e)
    插入操作:在表L的第i个位置上插入指定元素e

    #define MaxSize 10
    typedef struct{
    	Elemtype data[MaxSize];
    	int length;
    }SqList;
    
    bool ListInsert(Sqlist &L,int i,int e){
    	if(i<1||i>L.lengh+1) return false;
    	if(L.length>=maxSize)return false;
    	for(int j=L.lenth;j>=i;j--){	//将第i个元素及之后的数据后移
    		L.data[j]=L.data[j-1];
    	}
    	L.data[i-1]=e;					//在位置i处放入e
    	L.length++;						//长度加一
    	return true;
    }
    
    int main(){
    	SqList L;
    	InitList(L);
    	//插入元素代码
    	ListInsert(L,3,3);
    	return 0;
    }
    
  6. 插入操作的时间复杂度

    最好情况:新元素插入到表尾,不需要移动元素
    i=n+1,循环0次;最好时间复杂度=O(1)

    最坏情况:新元素插入到表头,要将原有的n个元素全部往后移动
    i=1,循环n次;最好时间复杂度=O(n)

    平均情况:假设新元素插入到任何一个未知的概率相同,i=1,2,3,...,length+1的概率是p=1/(1+n)
    平均循环次数=np+(n-1)p+...+1p=n(n+1)/2p=n(n+1)/2*(1/(n+1))=n/2

  7. 顺序表的基本操作——删除

    ListDelete(&L,i,&e)
    删除操作:删除表L中第i个位置的元素,并用e返回删除的值。

    #define MaxSize 10
    typedef struct{
    	Elemtype data[MaxSize];
    	int length;
    }SqList;
    
    bool ListDelete(SqList &L,int i,int &e){
    	if(i<1||i>L.length) return false;
    	e=L.data[i-1];
    	for(int j=i;j<L.length;j++){
    		L.data[j-1]=L.data[j];
    	}
    	L.length--;
    	return true;
    }
    
    int main(){
    	SqList L;
    	InitList(L);
    	//插入元素
    	int e=-1;
    	if(Listdelete(L,3,e))	printf("删除成功,元素值=%d",e);
    	else	printf("位序i不合法,删除失败");
    	return 0;
    }
    
  8. 删除操作的时间复杂度

    最好情况:删除表尾元素,不需要移动其他元素
    i=n,循环0次;最好时间复杂度O(1)

    最坏情况:删除表首元素,需要将后续的n-1个元素向后移动
    i=1,循环n-1次;最坏时间复杂度O(n)

    平均情况:假设删除任何一个元素的概率相同,即i=1,2,3...,length的概率都是p=1/n
    平均循环次数=(n-1)p+(n-2)p+...+1p=n(n-1)/2p=(n-1)/2--->平均时间复杂度O(n)

  9. 顺序表的查找——按位查找

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

    /*
    #define MaxSize 10
    typedef struct(){
    	Elemtype data[MaxSize];
    	int length;
    }SqList;
    */
    
    #define InitSize 10
    typedef struct{
    	Elemtype *data;
    	int MaxSize
    	int length;
    }SqList;
    
    ElemType GetElem(SqList L,int i){
    	return L.data[i-1];
    	/*
    		这里可以理解为何malloc函数返回存储空间的存储元素的骑士地址要转换为
    		与数据元素的数据类型相对应的指针
    	*/
    }
    

    时间复杂度O(1)
    由于顺序表的各个元素在内存中连续存放,因此可以根据起始地址和数据元素大小立即找到第i个元素——“随机存取”特性

  10. 顺序表的查找——按值查找

    LocateElem(L,e)
    按值查找操作:在表L中查找具有给定关键字值的元素

    #define InitSize 10
    typedef struct{
    	int *data;
    	int MaxSize
    	int length;
    }SqList;
    
    int LocateElem(SqList L,int e){
    	for(int i=0;i<L.length;i++){
    		if(e==L.data[i]) return i+1;
    	}
    	return 0;
    }
    

    调用:LocateElem(L,9);

    注:对于结构类型,不能直接用==来判断结构体的内容是否相等,要分别判断

    typeof struct{
    	int num;
    	int people;
    } Customer;
    
    void test(){
    	Customer a,b;
    	a.num=1;
    	a.people=1;
    	b.num=1;
    	b.people=1;
    	//错误方式:
    	/*
    	if(a==b)printf("相等");
    	*/
    	if(a.num==b.num&&a.people==b.people){
    		printf("相等");
    	}else{
    		printf("不相等");
    	}
    }
    

    时间复杂度
    最好:表首=O(1)
    最坏:表尾=O(n)
    平均:(1+2+...+n)/n=(n+1)/2->O(n)


单链表

  1. 单链表的定义

    每个节点除了存放数据外,还要存储指向下一个结点的指针

    /*
    struct LNode{				//定义单链表节点类型
    	ElemType data;			//每个节点存放一个数据元素
    	struct LNode *next;		//指针指向下一个及节点
    };
    typedef struct LNode LNode;
    typedef struct LNode *LinkList;
    */
    
    typedef struct LNode{
    	ElemType data;
    	struct LNode *next;
    }LNode,*LinkList;
    
    struct LNode *p=(struct LNode *)malloc(sizeof(struct LNode));
    

    实际上,要声明一个单链表时,只需声明一个头指针L,指向单链表的第一个节点

    LNode * L;	//声明指向单链表第一个节点的指针
    LinkList L;	//声明指向单链表第一个节点的指针——代码可读性更好
    

    eg:

    typedef struct LNode{
    	ElemType data;
    	struct LNode *next;
    }LNode,*LinkList;
    
    LNode * GetElem(LinkList L,int i){
    	int j=1;
    	LNode *p=L->next;
    	if(i==0) return L;
    	if(i<1) return null;
    	while(p!=null&&j<i){
    		p=p->next;
    		j++;
    	}
    	return p;
    }
    
  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) return (L==null);
    
  3. 带头结点的链表初始化

    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) return (L->next==null);
    
  4. 单链表的插入

    按位序插入(带头节点)

    ListInsert(&L,i,e)
    插入操作:在表L中的第i个位置插入指定元素e

    typedef struct LNode{
    	ElemType data;
    	struct LNode *next;
    }LNode,*LinkList;
    
    bool ListInsert(LinkList &L,int i,ELemtype 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){
    		p=p->next;
    		j++;
    	}
    	if(p==null) return false;	//i值不合法
    	LNode *s=(LNode *)malloc(sizeof(LNode));
    	s->data=e;
    	s->next=p->next;
    	p->next=s;			//将节点s连到p之后
    	return true;		//插入成功
    }	
    

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

    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){
    		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指向头结点,头结点是第0个节点,不存数据
    	
    	//循环找到i-1个节点
    	while(P!=null&&j<i-1){
    		p=p->next;
    		j++;
    	}
    	if(p==null) return false;	//i值不合法
    	LNode *s=(LNode *)malloc(sizeof(LNode));
    	s->data=e;
    	s->next=p->next;
    	p->next=s;			//将节点s连到p之后
    	return true;		//插入成功
    }	
    
  5. 指定节点的后插操作

    typedef struct LNode{
    	ElemType data;
    	struct LNode *next;
    }LNode,*LinkList;
    
    //后插操作:在p节点之后插入元素e
    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->next=p->next;
    	p->next=s;
    	return true;
    }
    

    代码优化:

    typedef struct LNode{
    	ElemType data;
    	struct LNode *next;
    }LNode,*LinkList;
    
    //在第i个位置元素插入元素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个节点,不存数据
    	
    	//循环找到i-1个节点
    	while(P!=null&&j<i-1){
    		p=p->next;
    		j++;
    	}
    	
    	/*代码封装
    		if(p==null) return false;	//i值不合法
    		LNode *s=(LNode *)malloc(sizeof(LNode));
    		s->data=e;
    		s->next=p->next;
    		p->next=s;			//将节点s连到p之后
    		return true;		//插入成功
    	*/
       
       return InsertNextNode(p,e);
    }	
    
    //后插操作:在p节点之后插入元素e
    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->next=p->next;
    	p->next=s;
    	return true;
    }
    
  6. 指定节点的前插操作

    bool InsertPriorNode(*P,e)
    前插操作:在p节点之前插入元素e

    由于无法知道前面的节点,需要传入头指针L
    bool InsertPriorNode(L,*P,e)
    但是随之而来的时间复杂度变成O(n)

    typedef struct LNode{
    	ElemType data;
    	struct LNode *next;
    }LNode,*LinkList;
    
    bool InsertPriorNode(LNode *P,ElemType 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->data=p->data;
    	p->data=e;
    	return true;
    }
    

    王道书版本:

    bool InsertPriorNode(LNode *P,LNode *s){
    	if(p==null||s==null) return false;
    	s->next=p->next;
    	p->next=s;
    	Elemtype temp=p->data;
    	p->data=s->data;
    	s->data=temp;
    	return true;
    }
    
  7. 按位删除节点

    ListDelete(&L,i,&e)
    删除操作:删除表L中第i个位置的元素,并用e返回删除元素的值

    #include <stdlib.h>
    typedef struct LNode{
    	ElemType data;
    	struct LNode *next;
    }LNode,*LinkList;
    
    bool ListDelete(LinkList &L,int i,ElemType &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){
    		p=p->next;
    		j++;
    	}
    	if(p==null) return false;		//i值不合法
    	if(p->next==null) return false;	//第i-1节点之后已无其他节点
    	
    	LNode *q=p->next;	//令q指向被删除节点
    	e=q->data;			//用e返回元素的值
    	p->next=q->next;	//将*q节点从链中断开
    	free(q);			//释放节点
    	return true;
    }
    
  8. 删除指定节点

    bool DeleteNode(LNode *P); //删除指定节点p

    #include <stdlib.h>
    typedef struct LNode{
    	ElemType data;
    	struct LNode *next;
    }LNode,*LinkList;
    
    bool DeleteNode(LNode *P){
    	if(p==null) return false;
    	LNode *q=p->next;		//令q指向*P的后继节点
    	p->data=p->next->data;	//和后继节点交换数据
    	p->next=q->next;		//将*p节点从链中断开
    	free(q);				//释放后继节点的存储空间
    	return true;
    }
    

    极限情况,如果p节点是最后一个节点
    神秘未知区域——>[x]——>null
    p->[x]
    *q->p.next=null;
    这些代码就会产生bug,这是,我们只能从表头开始依次查找p的前驱,时间复杂度O(n)

  9. 单链表的查找——按位查找

    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个节点,不存数据
    	
    	//循环找到i个节点
    	while(P!=null&&j<i){
    		p=p->next;
    		j++;
    	}
    	return p;
    }
    

    平均时间复杂度和最坏时间复杂度O(n)
    最好时间复杂度O(1)

    代码封装的好处:

    易扩展性,可读性,模块化,简洁...

    按位插入代码优化:

    typedef struct LNode{
    	ElemType data;
    	struct LNode *next;
    }LNode,*LinkList;
    
    //在第i个位置元素插入元素e
    bool ListInsert(LinkList &L,int i,ELemtype e){
    	if(i<1) return false;
    	
    	/*代码封装
    		LNode *p;int j=0;p=L;while(P!=null&&j<i-1){p=p->next;j++;}*/
    	LNode *p=GetElem(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;*/  
    	return InsertNextNode(p,e);
    }	
    
    //按位查找:获取表L中第i个位置的元素。
    LNode * GetElem(LinkList L,int i){
    	if(i<0) return null;
    	LNode *p;		//指针p当前扫描到的节点
    	int j=0;		//当前p指向的是第几个节点
    	p=L;			//L指向头结点,头结点是第0个节点,不存数据
    	
    	//循环找到i个节点
    	while(P!=null&&j<i){
    		p=p->next;
    		j++;
    	}
    	return p;
    }
    
    //后插操作:在p节点之后插入元素e
    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;
    }
    
  10. 单链表的查找——按值查找

    LocateElem(L,e)
    按值查找操作:在表L中查找具有给定关键字值的元素

    LNode *p=LocateELem(LinkList L,ElemType e){
    	LNode *P=L->next;
    	//从第一个几点开始查找数据为e的节点
    	while(P!=null&&p->data!=e) p=p->next;
    	return p;
    }
    
  11. 求表的长度

    int length(LinkList L){
    	int len=0;
    	LNode *p=L;
    	while(p->next!=null){
    		p=p->next;
    		len++;
    	}
    	return len;
    }
    
  12. 单链表的建立——尾插法

    typedef struct LNode{
    	ElemType data;
    	struct LNode *next;
    }LNode,*LinkList;
    
    void test(){
    	LinkList L;
    	InitList(L);
    	while(1){
    		Insert(L,length+1,e);
    		length++;
    		if(...)break;
    	}
    }
    

    每次都要从头开始遍历,时间复杂度是O(n^2)

    王道书版本,在表尾设置尾指针:

    LinkList ListTailInsert(LinkList &L){
    	int x;
    	L=(LinkList)malloc(sizeof(LNode));
    	LNode *s,*r=L;
    	scanf("%d",&x);
    	while(x!=9999){//9999结束
    		s=(LNode *)malloc(sizeof(LNode));
    		s->data=x;
    		r->next=s;
    		r=s;
    		scanf("%d",&x);
    	}
    	r->next=null;
    	return L;
    }
    
  13. 单链表的建立——头插法

    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;
    }
    
    while(<条件>){
    	InsertNextNode(p, e);	
    }
    

    王道书版本

    LinkList ListHeadInsert(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));
    		if(s==null) return false;		//内存分配失败
    		
    		s->data=x;			//用s保存数据元素x
    		s->next=L->next;
    		L->next=s;			//将新节点插入表中,L为头指针
    		scanf("%d",&x);
    	}
    	return L;
    }
    

双链表

  1. 双链表的定义

    typedef struct DNode{
    	ELemType data;
    	struct DNode *prior, *next;
    }DNode,*DLinkList;
    
    
  2. 双链表的初始化(带头结点)

    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;
    	L->next=null;
    	return true;
    }
    //判空操作
    bool Empty(DLinkList L) return (L->next==null);
    
    void tsetDLinkList(){
    	DLinkList L;
    	InitDLinkList(L);
    	//后续代码
    }
    
  3. 双链表的插入

    bool InsertNextDNode(DNode *p,DNode *s){
    	s->next=p->next;
    	p->next->prior=s;
    	s->prior=p;
    	p->next=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;
    }
    
  4. 双链表的删除

    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;
    }
    
    //销毁双链表
    void DestroyList(DLinkList &L){
    	while(L->next!=null) DeleteNextDNode(L);
    	free(L);
    	L=null;
    }
    
  5. 双链表的遍历

    //后向遍历
    while(p!=null) p=p->next;
    
    //前向遍历
    while(p!=null) p=p->prior;
    
  6. 双链表的特点
    双链表不可随机存取,按值查找、按位查找操作都只能拥便利的方法实现。时间复杂度O(n)


循环链表

  1. 循环单链表

    表尾的next指针指向头结点

    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=L;		//头结点next指向头结点
    	return true;
    }
    
    //判空操作
    bool Empty(LinkList L) return L->next==L;
    
    //判尾操作
    bool isTail(LinkList L,LNode *p) return L->next==L;
    

    从头部找到尾部,时间复杂度O(n);从尾部找到头部,时间复杂度O(1)
    很多时候,对链表的操作,都是在头部或者尾部,可以让L指向尾部(插入,删除时可能需要修改L)

  2. 循环双链表

    表头节点的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;
    	L->next=L;
    	return true;
    }
    
    void testDLinkList(){
    	DLinkList L;
    	InitDLinkList(L);
    	//后续代码...
    }
    
    //判空操作
    bool Empty(DLinkList L) return L->next==L;
    
    //判尾操作
    bool isTail(LinkList L,LNode *p) return L->next==L;
    
    //插入数据
    bool InsertNextDNode(DNode *p,DNode *s){
    	s->next=p->next;
    	p->next-prior=s;
    	s->prior=p;
    	p->next=s;
    }
    
    //删除数据
    bool DeleteNextDNode(DNode *p){
    	DNode *q=p->next;
    	p->next=q->next;
    	q->next->prior=p;
    	free(q);
    	return true;
    }
    

静态链表

  1. 什么是静态链表?

    分配一整片的内存空间,各个节点集中安置

  2. 如何定义一个静态链表

    #define MaxSize 10		//静态链表最大长度
    
    typedef struct {
    	ElemType data;		//存储数据元素
    	int next;			//下一个数组下标
    }SLinkList[MaxSize];
    
    //---
    #define MaxSize 10
    
    struct Node{
    	ElemType data;A
    	int next;
    };
    typedef struct Node SLinkList[MaxSize];
    
    void testSLinkList(){
    	SLinkList a;
    
    	struct Node a[MaxSize];
    }
    
  3. 基本操作的实现

    //初始化
    a[0]->next=-1;
    //同时把其他空闲节点的next=-2
    
    //查找
    /*
    	从头节点触发依次往后遍历节点,时间复杂度O(n)
    */
    
    //插入节点
    /*
    	找到空闲节点,存入数据元素
    	找到位序为i-1的节点
    	修改新节点的next=(i-1)->next
    	修改(i-1)->next=新节点的下标
    */
    
    //删除节点
    /*
    	从头找到前驱节点
    	修改前驱节点的游标
    	被删除节点的游标=-2
    */
    

顺序表和链表的比较

顺序表 链表
弹性 ×
增、删 ×
×
posted @ 2022-01-05 22:46  千树line  阅读(105)  评论(0)    收藏  举报