笔记:数据结构——第二章 线性表

数据结构与算法 复习笔记 第二章 线性表:概念/顺序表/链表 线性结构具有如下的结构特点: (1)均匀性:虽然不同线性表的数据元素可以是各种各样的,但在同一线性表中的各数据元素必定具有相同的数据类型和长度。 (2)有序性:各数据元素在线性表中都有自己的位置,且数据元素之间的相对位置是线性的、 按元素复杂度: 简单线性结构:顺序表、队列、散列表等 高级线性结构:广义表、多位数组等 2.1线性表的概念 2.1.1线性表的抽象数据类型 线性表(linear lis... 阅读详情

第二章 线性表

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

  • 线性表(Linear List)是具有相同数据类型的n(n>0)个数据元素的有限序列,其中n为表长,当n=0时线性表是一个空表;若用L命名线性表,则其一般表示为:L=(a1,a2,…,an)
  • ai是线性表中的“第i个”元素线性表中的位序;a1是表头元素,an是表尾元素;除第一个元素外,每个元素有且仅有一个直接前驱;除最后一个元素外,每个元素有且仅有一个直接后继
  • 注意:位序从1开始,数组下标从0开始
  • C语言函数的定义:<返回值类型>函数名(<参数1类型>参数1,<参数2类型>参数2,…)
  • 函数名、参数的形式和命名都可以改变,但要有可读性
  • 传入引用“&”,对参数的修改结果需要带回来

2.2 顺序表

2.2.1 顺序表的定义

  • 顺序表:用顺序存储的方式实现线性表顺序存储
  • 把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的领接关系来体现
  • C语言中知道一个数据元素的大小:sizeof(ElemType)
  • 顺序表中也可以存放结构类型的数据,命名为Customer的结构,其中包含两个整数,即此数据类型占8个字节
typedef sruct{
    int num;
    int people;
}Customer
*顺序表的实现——静态分配
#define MaxSize 10					//定义最大长度
typedef struct{
	ElemType data[MaxSize];			//用静态的"数组"存放数据元素
	int length; 					//顺序表的当前长度  
}SqList;       						//顺序表的类型定义(静态分配方式)
  • 给各个数据元素分配连续的存储空间,大小为MaxSize*sizeof(ElemType)
  • Sq:sequence ——顺序,序列
#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);					//初始化一个顺序表
    //尝试“违规”打印整个data数组
	for(int i=0;i<MaxSize;i++){
		printf("data[%d]=%d\n",i,L.data[i]);
	}
	return 0; 
}
  • 如果没有设置数据元素的默认值,内存中会有遗留的“脏数据”
  • 在内存中分配存储顺序表L的空间,包括MaxSize*sizeof(ElemType)和存储length的空间
*顺序表的实现——动态分配
#define InitSize 10 				//默认的最大长度
typedef struct{
	ElemType  *data;				//指示动态分配数组的指针
	int MaxSize;					//顺序表的最大容量
	int length;  					//顺序表的当前长度 
}SeqList; 							//顺序表的类型定义(静态分配方式)
  • C++动态申请和释放内存空间:new、delete关键字
  • C语言动态申请和释放内存空间:malloc、free函数
  • L.data =(ElemType*)malloc(InitSize*sizeof(ElemType)) ;malloc函数返回一个指针,需要强制转型为定义的数据元素类型指针(ElemType*)
#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; 
}
  • 虽然动态分配可以改变顺序表的大小,但时间开销大
  • realloc函数也可实现增加数组长度
  • 顺序表的特点:
    1. 随机访问 ,即可以在O(1)时间内找到第i个元素
    2. 存储密度高,每个节点只存储数据元素
    3. 拓展容量不方便
    4. 插入、删除操作不方便,需要移动大量元素

2.2.2(1) 顺序表的插入删除

*顺序表的基本操作——插入

插入操作:在表L中的第i个位置上插入指定元素e

#define MaxSize 10    
typedef struct{
    int data[MaxSize];  
    int Length;        
}SqList; 

//基本操作——在L的位序i处插入元素e
void ListInsert(SqList &L, int i, int e){ 
    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;
}

int main(){
    SqList L;          
    InitList(L);      
    //...插入几个元素
    ListInsert(L,3,3);
    return 0;
}

//bool类型实现插入操作——具有健壮性
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--){  
        L.data[j]=L.data[j-1];
    }
    L.data[i-1]=e; 
    L.length++;     
    return true;
}

  • 注意:位序、数组下标的关系,并从后面的元素依次移动

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

*顺序表的基本操作——删除

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

bool LisDelete(SqList &L, int i, int &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;
}

int main(){
    SqList L;          				
    InitList(L);    
    //...插入几个元素
    int e = -1;        				//用变量e把删除的元素“带回来”
    if(LisDelete(L,3,e))
        printf("已删除第三个元素,删除元素值=%d\n",e);
    else
        printf("位序i不合法,删除失败\n");
    return 0;
}
  • 注意:位序、数组下标的关系,并从前面的元素依次移动

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

2.2.2(2) 顺序表的查找

*顺序表的按位查找
  • 按位查找操作:获取表L中第i个位置的元素的值
//基于静态分配的代码实现
#define MaxSize 10            
typedef struct{
    ElemType data[MaxSize];  
    int Length;            
}SqList;                    

ElemType GetElem(SqList L, int i){
    return L.data[i-1];      		//注意是i-1
}

//基于动态分配的代码实现
#define InitSize 10 
typedef struct{
    ElemType *data;  
    int MaxSize;  
    int length;      
}SeqList;

ElemType GetElem(SqList L, int i){
    return L.data[i-1]; 			//指针也能用数组下标,和访问普通数组的方法一样
}
  • 平均时间复杂度 : T(n)=O(1);具有“随机存取”特性
*顺序表的按值查找
  • 按值查找操作:在表L中查找具有给定关键字值的元素
#define InitSize 10            
typedef struct{
    ElemTyp *data; 
    int Length;              
}SqList;   

int LocateElem(SqList L, ElemType e){
    for(int i=0; i<L.lengthl i++)
        if(L.data[i] == e)  
            return i+1;     		//数组下标为i的元素值等于e,返回其位序i+1
    return 0;               		//退出循环,说明查找失败
}
  • 基本数据类型:int、char、double、float等可以直接用运算符“==”比较;但如果为结构类型不能直接进行比较,需要依次对比各个分量来判断两个结构体是否相等,更好的办法为定义一个函数比较
typedef struct{
    int num;
    int people;
}Customer;

void test(){
    Customer a;
    a.num=1;a.people=1;
    Customer b;
    b.num=1;b.people=1;
    if (a.num == b.num && a.people == b.people){
        printf("相等");
    }else{
        printf("不相等");
    }
}

//更好的办法:定义一个函数
bool isCustomerEqual(Customer a,Customer b){
    if(a.num == b.num && a.people == b.people)
        return true;
    else
        return false;
}
  • 平均时间复杂度 : T(n)=O(n)

2.3 单链表

2.3.1 单链表的定义

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

  • 顺序表的优点:可随机存取,存储密度高;缺点:要求大片连续空间,改变容量不方便

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

  • typedef关键字——数据类型重命名;使用方法:typedef <数据类型> <别名> 例如:typedef int zhengshu;

*单链表的定义
typedef struct LNode{
	ElemType data;
	struct LNode *next;
}Lnode,*Linklist;	

//相当于上述代码
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;	

//初始化一个空的单链表
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;
}	
//或
bool Empty(LinkList L){
    return(L==NULL);     
}
*带头节点的单链表初始化
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;
}	
  • 带头结点的代码,操作更方便

2.3.2(1) 单链表的插入删除

*按位序插入(带头结点)
  • 插入操作:在表L中的第i个位置上插入指定元素e
  • 操作分析:
    1. 找到第i-1个结点
    2. 将新结点插入其后
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;
    int j=0;					//j表示当前p指向的是第几个结点
    p=L;						//p指向头结点L,头结点是第0个结点
    while(p!=NULL&&j<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;
}
  • 平均时间复杂度 : T(n)=O(n)
*按位序插入(不带头结点)
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;
    int j=1;
    p=L;						//p指向第一个结点(不是头结点)
    while(p!=NULL&&j<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;
    return true;
}
*指定结点的后插操作
  • 后插操作:在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;
    return true;
}

//“封装” 在第i个位置插入元素e也可以简化为
bool ListInsert(LinkList &L,int i,ElemType e){
    if(i<1)
        return false;
    LNode *p;
    int j=0;					//当前p指向的是第几个结点
    p=L;						//p指向头结点
    while(p!=NULL&&j<i-1){	
        p=p->next;
        j++;			
    }
    return InsertNextNode(p,e);	//进行后插操作
}
*指定结点的前插操作
  • 前插操作:在p结点之前插入元素e或结点s

  • 方法一:传入头结点,循环查找p的前驱q,再对q后插,时间复杂度为O(n)

  • 方法二:申请新结点,作为p的后继结点,交换两结点数据,时间复杂度为O(1)

//在p结点之前插入元素e
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;
}

//在p结点之前插入结点s
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;
}
*按位序删除(带头结点)
  • 删除操作:删除表L中第i个位置的元素,并用e返回删除元素的值
  • 操作分析:
    1. 找到第i-1个结点,指向第i+1个结点
    2. 释放第i个结点
bool ListDelete(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++;			
    }
    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;			//断开,指向第i+1个结点
    free(q);
    return true;
}
  • 平均时间复杂度 : T(n)=O(n)
*指定结点的删除
  • 方法一:传入头结点,循环查找p的前驱q,再删除

  • 方法二:q指针指向p的后继结点,后q数据复制到前p,指向q的下一结点并删除q

bool DeleteNode(LNode *p0){
    if(p==NULL)
        return false;
    LNode *q=p->next;
    p->data=p->next->data;		//存在bug:可能p指针为最后一个结点
    p->next=q->next;			//此时,只能用方法一
    free(q):
    return ture;
}
  • 如果p是最后一个结点,只能从表头开始依次寻找p的前驱
  • Tips:体会有“头结点”和“封装”的代码

2.3.2(2) 单链表的查找

*按位查找
  • 按位查找:获取表L中第i个位置元素的值,返回第i个元素(与上述查找第i-1个类似)
  • 平均时间复杂度 : T(n)=O(n)
LNode *GetELem(LinkList L,int i){
    if(i<0)						//考虑i=0时,为头结点情况;p=L指向头结点
        return false;
    LNode *p;
    int j=0;				
    p=L;						
    while(p!=NULL&&j<i){		//i值不合法(大于链长);循环找到第i个结点
        p=p->next;
        j++;			
    }
    return p;
}

//第二种写法
LNode *GetELem(LinkList L,int i){
    int j=1;					//p指向首元结点
    LNode *p=L->next;
    if(i==0)					//考虑i=0时,为头结点情况;
        return L;
    if(i<1)
        return NULL;					
    while(p!=NULL&&j<i){	
        p=p->next;
        j++;			
    }
    return p;
}

//在第i个位置插入元素e(带头结点)
bool ListInsert(LinkList &L,int i,ElemType e){
    if(i<1)
        return false;
   	
    LNode *p=*GetELem(L,int i-1);			
    
    return InsertNextNode(p,e);
}

//补充上述return后的函数
bool InsertNextNode(LNode *p,ElemType e){
    if(p==NULL)					//有必要,具有健壮性
        return false;			//考虑到GetELem函数的非法情况会返回NULL
     LNode *s=(LNode*)malloc(sizeof(LNode));	
    if(s==NULL)					
        return false;
    s->data=e;					
    s->next=p->next;
    p->next=s;
    return true;
}


//删除第i个位置的元素
bool ListDelete(LinkList &L,int i,ElemType &e){
    if(i<1)
        return false;
   
    LNode *p=*GetELem(L,int 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 ture;
}
  • 体会“封装”的好处:避免重复代码,简介、易维护
*按值查找
  • 按值查找:在表L中查找给定关键字值e的元素
LNode *LocateElem(LinkList L,ElemType e){
    LNode *p=L->next;
    while(p!=NULL&&p->data!=e)
        p=p->next;
    return p;
}
  • 如果数据类型为结构类型就不能使用!=符号
*求表的长度
int Length(LinkList L){
    int len=0;
    LNode *p=L;
    while(p->next!=NULL){
        p=p->next;
        len++;
    }
    return len;
}
  • 平均时间复杂度 : T(n)=O(n)

2.3.2(3) 单链表的建立

*尾插法
  • 操作分析:

    1. 初始化单链表 InitList(L)

    2. 设置变量length记录链表长度

    3. while{
          //每次取一个元素e;
          //调用ListInsert(L,length+1,e);把元素插到尾部
          //length++;
      }
      
  • ListInsert(L,length+1,e)每次找表尾都要循环一次,时间复杂度O(n);可以设置一个表尾指针,然后进行后插操作

LinkList List_TailInsert(LinkList &L){		//尾插法 正向建立单链表
    int x;									//设ElemType为整形
    L=(LinkList)malloc(sizeof(LNode));		//建立头结点,初始化空表
    LNode *s,*r=L;							//s为新结点,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;
}
*头插法
  • 操作分析:

    1. 初始化单链表 InitList(L)

    2. while{
          //每次取一个元素e;
          //调用InsertNextNode(L,e);在头结点进行后插操作;
          //length++;
      }
      
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;
        scanf("%d",&x);
    }
    return L;
}
  • 重要应用:单链表的逆置,给定链表逆置的核心逻辑不改变

  • 方法一:新建链表,按顺序取原链表元素,再采用头插法插入新链表

  • 方法二:按顺序取结点插到头结点后,可以让链表原地逆置

2.3.3 双链表

*双链表的定义
  • 双链表可进可退,但存储密度会更低
typedef struct DNode{
    ElemType data;							//数据域
    struct DNode *prior,*next;				//前驱和后继指针
}DNode,*DLinkList;
*双链表的初始化
typedef struct DNode{
    ElemType data;
    struct DNode *prior,*next;
}DNode,*DLinkList;

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

void testDLinkList(){
    DLinkList L;
    InitDLinkList(L);
    //...后续代码...
}

//判断双链表是否为空
bool Empty(DLinkList L){
    if(L->next==NULL)
        return ture;
    else
        return false;
}
*双链表的插入
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的后继结点不为空
        p->next->prior=s;
    s->prior=p;
    p->next=s;
    return ture;
}
  • 按位序插入:找到某一个位序的前驱节点,进行后插操作

  • 前插操作:找到给定结点的前驱节点,进行后插操作

*双链表的删除
bool DeleteNextDNode(DNode *p){		//p为删除结点前驱
    if(p==NULL)   
        return false;
    DNode *q=p->next;
    if(q==NULL)   					//p没有后继结点,删除位置q为空
        return false;
    p->next=q->next;
    if(q->next!=NULL)				//删除位置q的后继结点不为空
        q->next->prior=p;
    free(q);
    return true;
}

//销毁一个双链表
void DestoryList(DLinkList &L){
    while(L->next!=NULL)
        DeleteNextNNode(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;
}
  • 双链表不可随机存取,按位查找、按值查找操作都只能用遍历的方式实现
  • 平均时间复杂度 : T(n)=O(n)
  • 在双链表的插入和删除操作时,注意边界情况,如插入和删除结点为最后一个结点位置

2.3.4 循环链表

  • 循环单链表:表尾结点的next指针指向头结点
*循环单链表的定义
typedef struct LNode{
    ElemType data;
    struct LNode *next;
}LNode,*LinkList;
*循环单链表的初始化
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 ture;
}

//判断循环单链表是否为空
bool Empty(LinkList L){
    if(L->next==L)
        return ture;
    else
        return false;
}	

//判断结点p是否为循环单链表的表尾结点
bool isTail(LinkList L,LNode *p){
    if(p->next==L)
        return ture;
    else
        return false;
}
  • 如果单链表频繁在表头、表尾操作,可以让L指向表尾元素(插入和删除时可能需要修改L)
*循环双链表的初始化
typedef struct DNode{
    ElemType data;
    struct DNode *prior,*next;
}DNode,*DinkList;

//初始化一个循环双链表
bool InitDLinkList(DLinkList &L){
	L = (LNode*)malloc(sizeof(Lnode));
	if(L==NULL){
		return false;  				 
	}
	L->next = L; 					//头结点的prior指向头结点;双链表指向NULL
	L->prior = L;					//头结点的next指向头结点
	return true;
}

//判断循环双链表是否为空
bool Empty(DLinkList L){
	if(L->next == L)				//判断条件与双链表不同
		return true;
	else
		return false;
} 

//判断结点p是否为循环双链表的表尾结点
bool isTail(LinkList L,LNode *p){
	if(p->next == L)
		return true;
	else
		return false;
} 
  • 在使用循环双链表时,不需要考虑边界情况,如插入和删除结点为最后一个结点

2.3.5 静态链表

  • 静态链表:分配一整片连续的内存空间,各个结点集中安置
  • 单链表的指针域:指向下一个结点的指针(地址);静态链表的指针:指向下一个结点的数组下标
  • 0号结点为头结点,游标充当指针,游标为-1表示已经到达表尾
*静态链表的定义
#define MaxSize 10 
struct Node{						//静态链表结构类型的定义 
	ElemType data; 					//存储数据元素 
	int next;						//下一个元素的数组下标 
}; 

void testSLinkList(){
    struct Node a[MaxSize];
    //...后续代码
}

//另一种写法
#define MaxSize 10 
struct Node{		 
	ElemType data;
	int next;	
}SLinkList[MaxSize];
//等价于
#define MaxSize 10 
struct Node{		 
	ElemType data;
	int next;	
}; 
typedef struct Node SLinkList[MaxSize];	//可用SLinkList定义“一个长度为MaxSize的Node型数组”

//另一种写法
#define MaxSize 10      

typedef struct{           
    ELemType data;        
    int next;            
}SLinkList[MaxSize];

void testSLinkList(){
    SLinkList a;
}
  • 注意:SLinkList a 强调a是静态链表;struct Node a 强调a是一个Node型数组、
*静态链表的基本操作
  • 初始化静态链表:把a[0]的next设为-1
  • 查找某个位序的结点(不是数组下标,是各个结点在逻辑上的顺序):从头结点出发挨个往后遍历结点;时间复杂度 T(n)=O(n)
  • 插入位序为i的结点:
    1. 找到一个空的结点,存入数据元素
    2. 从头结点出发找到位序为i-1的结点
    3. 修改新结点的next
    4. 修改i-1号结点的next
  • 删除某个结点:
    1. 从头结点出发找到前驱结点
    2. 修改前驱节点的游标
    3. 被删除节点next设为-2
  • 静态链表:用数组的方式实现的链表
  • 优点:增删操作不需要大量移动元素
  • 缺点:不能随机存取,只能从头结点开始依次往后查

2.3.6 顺序表和链表的比较

  • 逻辑结构:顺序表和链表都属于线性表,都是线性结构

  • 存储结构:

    1. 顺序表——顺序存储;优点:支持随机存取,存储密度高;缺点:大片连续空间分配不方便,改变容量不方便
    2. 链表——链式存储;优点:离散的小空间分配方便,改变容量方便;缺点:不可随机存取,存储密度低
  • 基本操作——创建:

    1. 顺序表:需要预分配大片连续空间。若分配空间过小,则之后不方便拓展容量;若分配空间过大,则浪费内存资源
    2. 静态分配:静态数组,容量不可改变
    3. 动态分配:动态数组,容量可以改变,但是需要移动大量元素,时间代价高(malloc(),free())
    4. 链表:只需要分配一个头结点或者只声明一个头指针
  • 基本操作——销毁:

    1. 顺序表:修改 Length = 0

      静态数组——系统自动回收空间

      typedef struct{
          ElemType *data;
          int MaxSize;
          int length;
      }SeqList; 
      

      动态分配:动态数组——需要手动free()

      //创建动态数组
      L.data = (ELemType *)malloc(sizeof(ElemType) *InitSize)
      //销毁动态数组
      free(L.data);
      //malloc() 和 free() 必须成对出现
      
  • 基本操作——增/删:

    1. 顺序表:插入/删除元素要将后续元素后移/前移;时间复杂度=O(n),时间开销主要来自于移动元素
    2. 链表:插入/删除元素只需要修改指针;时间复杂度=O(n),时间开销主要来自查找目标元素
  • 基本操作——查:

    1. 顺序表:按位查找为O(1),按值查找为O(n);若表内元素有序,可在O(log2n)时间内找到
    2. 链表:按位查找和按值查找均为O(n)
H5获取手机相机或相册图片两种方式-Android通过webview传递多张照片给H5 H5调用公共获取图片文件方法,在android手机端,H5主要依赖Webview,这边在webview声明并重写该方法onShowFileChooser使用工具类打开相机或相册,可以两种方式安卓原生方式或者利用第三方组件库方式选中图片,返回uri列表给H5H5收到uri照片列表,并且使用前端方式展示创造价值,乐哉分享! 阅读详情

相关推荐

Windows下玩转DeepSeek-R1:Ollama+Docker配置RAGFlow的3种网络调试方案

本文详细介绍了在Windows环境下使用Ollama和Docker配置RAGFlow的三种网络调试方案,帮助开发者解决本地部署AI知识库系统时的网络隔离问题。通过host.docker.internal直连、自定义Docker网桥网络和端口映射与反向代理三种方法,实现DeepSeek-R1模型与RAGFlow的高效通信,提升本地AI应用的开发效率。

weixin_28347369的博客 247

严蔚敏数据结构线性表

与颜奶奶书上编程风格类似,会持续更新

laohuchishizi的博客 177

Cortex-M TrustZone实战:从硬件隔离到安全交互的嵌入式开发指南

在嵌入式系统开发中,硬件安全隔离是构建可信执行环境(TEE)的核心技术。其原理是通过处理器硬件机制,将系统资源划分为安全与非安全域,实现内存、外设和代码的强制访问控制。这项技术的核心价值在于,它能在单一芯片上为资源受限的物联网(IoT)设备、智能传感器等提供芯片级的安全基础,无需依赖外置安全元件,从而降低BOM成本并简化设计。典型的应用场景包括保护设备密钥、实现安全启动、构建安全服务(如加密、密钥管理)以供应用程序调用。本文聚焦于Arm Cortex-M系列微控制器上的TrustZone技术实现,针对开发中

weixin_42639246的博客 300

数据结构---线性表(结构体实现)

一、线性表的定义 线性表(Linear List)是具有相同特性的数据元素的一个有限序列 二、线性表的顺序存储结构——顺序表 元素类型:ElemType 每个元素占用存储空间大小(即字节数):sizeof(ElemType) 整个线性表占用存储空间大小:n*sizeof(ElemType)。n表示线性表的长度。 C/C++中,用数组存放线性表中的元素及其逻辑关系 数组大小:MaxSi...

malechi的博客 1万+

编写用户帮助/操作手册指南

一个新系统,需要写用户操作手册,该从何下笔?本篇是一篇教你编写用户帮助/操作手册的指南~

pytester的博客 3万+

PX4(CUAV v6x 飞控)+ QAV250 机架无人机组装测试教学(无人机电机转向不对,如何反转电机?)

本文介绍无人机的组装与飞控调试:1.无人机的硬件组装。2.无人机飞控软件的下载,电机、遥控器、电调的校准与调试。

weixin_74185504的博客 2891

【开卷数据结构】线性表

开卷数据结构 - 1 - 线性表

Ceylan__的博客 4160

网络安全 —— 实验(基于华为eNSP模拟器)

8. 实验八 —— 通过路由的远程管理(基于实验七的基础配置)8. 实验八 —— 通过路由的远程管理(基于实验七的基础配置)6. 实验六 —— 交换机的远程控制(直接控制和间接控制)6. 实验六 —— 交换机的远程控制(直接控制和间接控制)5. 实验五 —— 将交换机作为PC端通过交换机相连。5. 实验五 —— 将交换机作为PC端通过交换机相连。4. 实验四 —— 两个PC机通过两台交换机相连。2. 实验二 —— 两个PC机通过交换机相连。2. 实验二 —— 两个PC机通过交换机相连。

这个家伙看起来很懒,只想把你留下来哦 9343

数据结构第二章(顺序表部分)学习笔记

一、线性表---顺序表部分 知识回顾 数据结构: 逻辑结构 线性结构 :线性表、栈、队列、串、数组 非线性结构 :树、图 存储结构 链式存储 顺序存储 操作 :增、删、查、改 1)线性表的定义 线性表是具有相同数据类型(相同指:每个数据所占的空间一样大)的n(n≥0)个数据元素的有限序列(有次序的),其中n为表长,当n = 0时线性表是一个空表。若用L命名线性表,则其一般表示为 L ...

qq_62175345的博客 3118

机械臂速成小指南(七):从旋转矩阵到齐次变换——机械臂位姿的数学表达

本文详细介绍了机械臂位姿描述的数学基础,包括旋转矩阵、欧拉角和齐次变换等核心概念。通过解析旋转矩阵的几何意义和合成方法,以及欧拉角的直观描述,帮助读者理解机械臂位姿的数学表达。文章还探讨了齐次变换在工程实践中的应用,并提供了数值稳定性、坐标系约定和计算效率等实用建议。

cuda7parallel的博客 835

Spring AI Alibaba实战训练营-16 NL2SQL (自然语言转 SQL)应用开发指南

本文介绍了一个基于AI的智能BI应用开发项目,主要实现自然语言转SQL(NL2SQL)功能,包含对话模块、MCP模块、向量数据管理模块。项目采用Spring AI Alibaba框架,集成通义千问大模型,支持内存向量库和AnalyticDB向量数据库两种存储方案。重点展示了chat模块的三个控制器实现:AnalyticNl2SqlController用于生产环境,SimpleChatController适用于开发测试,Nl2sqlForGraphController支持基于图的复杂对话流程。系统通过将业务知

program哲学 1589

《数据结构》线性表的定义、基本操作以及顺序表示

注意:线性表中数据元素的位序从“1”开始,而数组下标从“0”开始。 1)表中元素个数有限; 2)表中元素有逻辑上的顺序性,表中元素有先后次序; 3)表中元素都是数据元素,每个元素都是单个元素; 4)表中元素数据类型都相同,每个元素占有相同大小的存储空间; 5)表中元素具抽象性,仅讨论元素间的逻辑关系,不考虑元素表示的是什么内容。注意:线性表是一种逻辑结构,表示元素之间一对一的相邻关系。 顺序表和链表是指存储结构。 1)InitList(&L

weixin_43904440的博客 2200

Vue - 解决报错 Error: Cannot find module ‘XXX\@vue\cli-service\bin\vue-cli-service.js‘(vue项目编译运行出错,启动失败)

vue报错,Error: Cannot find module ‘@vue\cli-service\bin\vue-cli-service.js‘,vue2项目启动报错,vue3项目运行出错,vue执行npm run serve报错,找不到@vue/cli-service模块,vue项目关于:Error: Cannot find module ‘D:\@vue\cli-service\bin\vue-cli-service.js‘的解决方案,vue项目跑不起来了,Vue项目报错,跑项目报错Cannot fi

🏆 前端领域优质创作者 3115

02专升本数据结构笔记 第二章:线性表

线性表是由n(n≥0)个类型相同的数据元素a1,a2,…,an组成的有限序列,数据元素之间是一对一的关系,记作(由n(n≥0)个类型相同的数据元素,组成的有限序列,数据元素之间是一对一的关系,为线性表)可以理解为有序数组。

lxttzlove的博客 2349

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

数据结构笔记(王道考研) 第二章:线性表

数据结构笔记(王道考研) 第二章:线性表

zimuzi2019的博客 3186

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

数据结构详解笔记:第二章 线性表

本章主要详解了线性表(逻辑结构)对应的顺序表和链表(存储结构)的实现方式,其中包含增删改查的操作的多种实现方法及代码,最后还介绍了一些特殊链表的引入,以解决现有的存储结构存在的一些问题缺陷。 文章目录总览:①线性表的定义和基本操作定义:线性表的特点:线性表的九种基本操作:②线性表的顺序表示顺序表的定义:数组静态分配:数组动态分配:顺序表的基本操作的实现:插入操作:删除操作:按值查找:顺序表的合并操作:③线性表的链式表示单链表的定义:单链表的结构:引入头结点的单链表:单链表的基本操作的实现:头插法建立单链表:

Spuer_Tiger 686

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

(免费)02专升本数据结构笔记 第二章:线性表

线性表是由n(n≥0)个类型相同的数据元素a1,a2,…,an组成的有限序列,数据元素之间是一对一的关系,记作(由n(n≥0)个类型相同的数据元素,组成的有限序列,数据元素之间是一对一的关系,为线性表)可以理解为有序数组。

lxttzlove的博客 1161

数据结构学习笔记(第二章:线性表)

第二章:线性表2.1 线性表的概念一、pandas是什么?二、使用步骤1.引入库2.读入数据总结 2.1 线性表的概念 <font color= >提示:这里可以添加本文要记录的大概内容: 例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。 提示:以下是本篇文章正文内容,下面案例可供参考 一、pandas是什么? 示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。 二、使用步骤 1.引入

qq_44993579的博客 799

数据结构第二章笔记一:线性表

2.1 线性表的类型定义 线性表(Linear List) :由n(n≧)个数据元素(结点)a1,a2, …an组成的有限序列。其中数据元素的个数n定义为表的长度。当n=0时称为空表,常常将非空的线性表(n>0)记作: (a1,a2,…an) 这里的数据元素ai(1≦i≦n)只是一个抽象的符号,其具体含义在不同的情况下可以不同。 例1、26个...

weixin_30295091的博客 155

数据结构与算法学习笔记——第二章:线性表-4-线性表的应用

线性表的合并 有序表的合并

预见未来to50的专栏 178

数据结构与算法学习笔记——第二章:线性表-1-线性表的基本概念

线性表的基本概念

预见未来to50的专栏 176

数据结构知识总结笔记------第二章:线性表 线性表的基本概念与实现、线性表的顺序表示、线性表的链式表示、顺序表和链表的比较

第二章 线性表 2.1、线性表的基本概念与实现 2.1.1、线性表的定义 线性表是具有相同数据类型的n(n≥0)个数据元素的有序序列,其中n为表长,当n=0时该线性表就成为一个空表。 若用L来表示线性表,则可以表示为 L=(A1, A2,A3,…,An) 上面的表达式中A1是唯一的“第一个”数据元素,称为表头元素;An是唯一的“最后一个”数据元素,称为表尾元素。除了表头元素外,每一个元素都有且只有一个直接前驱,除了表尾元素外,每一个元素都有且只有一个直接后继。总的来说就是除了表头元素和表尾元素,剩余的元素都

DKPT的博客 1979

数据结构与算法学习笔记——第二章:线性表-2-基于线性表操作的简单应用

基于线性表操作的简单应用

预见未来to50的专栏 296

数据结构与算法学习笔记——第二章:线性表-3-线性表的存储结构及基本操作实现——顺序存储结构

顺序存储结构 线性表的基本操作 合并线性表算法 线性表的顺序存储结构(可以随机存取数据) 顺序存储结构的存储 顺序存储结构的实现 实现实例(C语言描述) 线性表顺序存储结构上基本操作的实现——1. 初始化(创建线性表) 2. 查找 按位置查找 按值查找 插入元素操作 插入算法的时间性能分析 删除元素操作 ...

预见未来to50的专栏 222

数据结构与算法学习笔记——第二章:线性表-5-栈——栈的定义及其顺序实现

栈的定义 回顾:线性表——n个相同元素的有限序列 数据结构关联图 操作受限的线性表——栈与队列 栈的定义 栈的抽象数据类型定义 栈的基本操作 测试栈的基本操作 讨论 栈的顺序实现 顺序栈入栈操作的实现 顺序栈出栈操作的实现 顺序栈取栈顶元素操作的实现 多个顺序栈空间共享 ...

预见未来to50的专栏 255

王道数据结构课代表 - 考研数据结构 第二章 线性表 究极精华总结笔记(C版本)

数据结构与算法 笔记导航🚥🚥🚥 🥬 第一章 绪论 🥕 第二章 线性表 🥪 第三章 栈和队列 🍊 第四章 串-KMP(看毛片算法) 🍒 第五章 树和二叉树 🍀 第六章 图 🍚 第七章 查找(B树、散列表) 🧄 第八章 排序 (内部排序:八大排序动图演示与实现 + 外部排序) 🍔 数据结构与算法 复试精简笔记 🎨 408 全套初复试笔记汇总 传送门 🏃‍🏃‍🏃‍ 如果本篇文章对大家起到帮助的话,跪求各位帅哥美女们,求赞👍 、求收藏、求关注!

clzZ2的博客 1万+
上一篇: 笔记:数据结构——第一章 绪论
下一篇: 笔记:数据结构——第三章 栈和队列
ITS_Oaij
博客等级 码龄6年 1857粉丝 51原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值