数据结构 第2章:线性表

news2024/11/27 8:45:48

文章目录

  • 2.1 线性表的定义和操作
    • 2.1.1 线性表的基本概念
    • 2.1.2 线性表的基本操作
  • 2.2. 顺序表
    • 2.2.1. 顺序表的基本概念
    • 2.2.2. 顺序表的实现
    • 2.2.3. 顺序表的基本操作
  • 2.3 链表
    • 2.3.1 单链表的基本概念
    • 2.3.2 单链表的实现
    • 2.3.3 单链表的插入
    • 2.3.4. 单链表的删除
    • 2.3.5. 单链表的查找
    • 2.3.6. 单链表的建立
    • 2.3.7. 双链表
    • 2.3.8 循环链表
    • 2.3.9. 静态链表
    • 2.3.10. 顺序表和链表的比较

2.1 线性表的定义和操作

2.1.1 线性表的基本概念

  1. 线性表:是具有相同数据类型的 n 个数据元素的有限序列。

  2. 特点:

存在惟一的第一个元素。
存在惟一的最后一个元素。
除第一个元素之外,每个元素均只有一个直接前驱。
除最后一个元素之外,每个元素均只有一个直接后继。

  1. 线性表的存储结构:
    顺序存储结构:顺序表
    链式存储结构:链表

2.1.2 线性表的基本操作

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

  2. DestroyList(&L):销毁表。并释放线性表 L 占用的内存空间。

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

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

  5. LocateElem(L, e):按值查找。在表 L 中查找具有给定元素值的元素。

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

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

  8. PrintList(L):打印表。按顺序输出线性表 L 的所有元素值。

  9. Empty(L):判断是否为空。若 线性表L 为空表,则返回 true,否则返回 false。

操作数据结构的思路:创销、增删改查

2.2. 顺序表

2.2.1. 顺序表的基本概念

  1. 顺序表:用顺序存储的方式实现线性表。顺序存储,将逻辑上相邻的元素存储在相邻的物理位置上。
  2. 特点:
  1. 随机访问,即可以在 O ( 1 )时间内找到第 i 个元素。
  2. 存储密度高,每个节点只存储数据元素。
  3. 拓展容量不方便(即使使用动态分配的方式实现,拓展长度的时间复杂度也比较高,因为需要把数据复制到新的区域)。
  4. 插入删除操作不方便,需移动大量元素:O ( n )

2.2.2. 顺序表的实现

静态实现

#define MaxSize 10 // 定义最大长度 

typedef struct {
	int data[MaxSize]; // 使用静态的数组存放数据元素 
	int length; // 顺序表的当前长度 
}SqList;

// 初始化顺序表 
void InitList(SqList &L) {
	L.length = 0; // 顺序表初始长度为0 
}

int main() {
	SqList L; // 声明一个顺序表 
	InitList(L); // 初始化顺序表 
	return 0;
}

动态实现

#define InitSize 10 // 顺序表的初始长度

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

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

// 增加动态数组的长度 
void IncreaseSize(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() {
	SeqList L; // 声明一个顺序表 
	InitList(L); // 初始化顺序表 
    ...
	IncreaseSize(L, 5);
	return 0;
}

malloc() 函数的作用:会申请一片存储空间,并返回存储空间第一个位置的地址,也就是该位置的指针。

2.2.3. 顺序表的基本操作

  1. 插入
#define MaxSize 10 // 定义最大长度 

typedef struct {
	int data[MaxSize]; // 用静态的数组存放数据元素 
	int length; // 顺序表的当前长度 
}SqList;

// 在顺序表i位置插入e
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;
} 

int main() {
	SqList L;
	InitList(L);
	ListInsert(L, 3, 3);
	return 0; 
} 

时间复杂度

  1. 最好时间复杂度:O ( 1 )
  2. 最坏时间复杂度:O ( n )
  3. 平均时间复杂度:O ( n )
  1. 删除
#define MaxSize 10

typedef struct {
	int data[MaxSize];
	int length;
} SqList;

// 删除顺序表i位置的数据并存入e
bool ListDelete(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--;
	return true; 
}

int main() {
	SqList L;
	InitList(L);
	int e = -1;
	if (ListDelete(L, 3, e))
		printf("已删除第3个元素,删除元素值为%d\n", e);
	else
		printf("位序i不合法,删除失败\n"); 
	return 0; 
} 

时间复杂度

  1. 最好时间复杂度:O ( 1 )
  2. 最坏时间复杂度:O ( n )
  3. 平均时间复杂度:O ( n )
  1. 按位查找
// 静态分配的按位查找
#define MaxSize 10

typedef struct {
	ElemType data[MaxSize]; 
	int length;
}SqList;

ElemType GetElem(SqList L, int i) {
	return L.data[i-1];
}
// 动态分配的按位查找
#define InitSize 10

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

ElemType GetElem(SeqList L, int i) {
	return L.data[i-1];
}

时间复杂度: O ( 1 )

  1. 按值查找
#define InitSize 10

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

// 查找第一个元素值为e的元素,并返回其位序 
int LocateElem(SqList L, ElemType e) {
	for (int i = 0; i < L.length; i++)
		if (L.data[i] == e)
			return i+1; // 数组下标为i的元素值等于e,返回其位序i+1 
	return 0; // 没有查找到 
}

在《数据结构》考研初试中,手写代码可以直接用“==”,无论 ElemType 是基本数据类型还是结构类型
时间复杂度

  1. 最好时间复杂度:O ( 1 ) O(1)O(1)
  2. 最坏时间复杂度:O ( n ) O(n)O(n)
  3. 平均时间复杂度:O ( n ) O(n)O(n)

2.3 链表

2.3.1 单链表的基本概念

在这里插入图片描述

  1. 单链表:用链式存储实现了线性结构。一个结点存储一个数据元素,各结点间的前后关系用一个指针表示。
  2. 特点
    优点:不要求大片连续空间,改变容量方便。
    缺点:不可随机存取,要耗费一定空间存放指针。
  3. 两种实现方式
    带头结点,写代码更方便。头结点不存储数据,头结点指向的下一个结点才存放实际数据。
    不带头结点,麻烦。对第一个数据结点与后续数据结点的处理需要用不同的代码逻辑,对空表和非空表的处理需要用不同的代码逻辑。

2.3.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)
}

带头结点的单链表:

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.3 单链表的插入

  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;           //指针p指向当前扫描到的结点    
    int j=0;            //当前p指向的是第几个结点   
    p = L;              //循环找到第i-1个结点    
    while(p!=NULL && j<i-1){       //如果i>lengh,p最后会等于NULL 
        p = p->next;              
        j++;      
    }       
    //p值为NULL说明i值不合法   
    if (p==NULL)            
        return false;       
    //在第i-1个结点后插入新结点  
    LNode *s = (LNode *)malloc(sizeof(LNode)); 
    s->data = e;     
    s->next = p->next; 
    p->next = s;       
    //将结点s连到p后      
    return true;
}

时间复杂度

  1. 最好时间复杂度:O ( 1 )
  2. 最坏时间复杂度:O ( n )
  3. 平均时间复杂度:O ( n )
  1. 按位序插入(不带头结点)
typedef struct LNode{     
    ElemType data;      
    struct LNode *next;
}LNode, *LinkList;

//在第i个位置插入元素e
bool ListInsert(LinkList &L, int i, ElemType e){   
    //判断i的合法性     
    if(i<1)      
        return false; 
    //需要判断插入的位置是否是第1个     
    if(i==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;          
    //循环找到第i-1个结点    
    while(p!=NULL && j<i-1){     //如果i>lengh,p最后会等于NULL    
        p = p->next;             
        j++;      
    }       
    //p值为NULL说明i值不合法   
    if (p==NULL)           
        return false;      
    //在第i-1个结点后插入新结点   
    LNode *s = (LNode *)malloc(sizeof(LNode));  
    s->data = e;      
    s->next = p->next;  
    p->next = s;        
    return true;
}

时间复杂度

  1. 最好时间复杂度:O ( 1 )
  2. 最坏时间复杂度:O ( n )
  3. 平均时间复杂度:O ( n )

除非特别声明,否则之后的代码都默认为带头结点!
3. 指定结点的后插操作

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;
}

// 按位序插入的函数中可以直接调用后插操作
bool ListInsert(LinkList &L, int i, ElemType e){  
    if(i<1)            
        return False;
    LNode *p;     
    //指针p指向当前扫描到的结点 
    int j=0;        
    //当前p指向的是第几个结点 
    p = L;       
    //循环找到第i-1个结点   
    while(p!=NULL && j<i-1){ 
        //如果i>lengh, p最后会等于NULL   
        p = p->next;        
        j++;       
    }       
    return InsertNextNode(p, e)
}

时间复杂度:O ( 1 )

  1. 指定结点的前插操作:

如果传入头指针,就可以循环整个链表找到指定结点p的前驱结点q,再对q进行后插操作;
如果不传入头指针,可以在指定结点p后插入一个结点s,并交换两个结点所保存的数据,从而变相实现指定结点的前插操作。

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

// 在结点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插入结点p之后    
    s->next = p->next;   
    p->next = s;       
    // 交换两个结点中的数据  
    s->data = p->data;   
    p->data = e;       
    return true;
}

时间复杂度:O ( 1 )

2.3.4. 单链表的删除

  1. 按位序删除
typedef struct LNode{       
    ElemType data;    
    struct LNode *next;}LNode, *LinkList;

// 删除第i个结点并将其所保存的数据存入e
bool ListDelete(LinkList &L, int i, ElemType &e){      
    if(i<1)             
        return false;     
    LNode *p;       //指针p指向当前扫描到的结点     
    int j=0;        //当前p指向的是第几个结点    
    p = L;         
    //循环找到第i-1个结点     
    while(p!=NULL && j<i-1){   
        //如果i>lengh,p和p的后继结点会等于NULL        
        p = p->next;            
        j++;      
    }       
    if(p==NULL)       
        return false;    
    if(p->next == NULL)        
        return false;    	   
    //令q暂时保存被删除的结点   
    LNode *q = p->next;    
    e = q->data;     
    p->next = q->next;      
    free(q)     
    return true;
}

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

  1. 删除指定结点
  1. 如果传入头指针,就可以循环整个链表找到指定结点p的前驱结点q,再对p进行删除操作;
  2. 如果不传入头指针,可以把指定结点p的后继结点q删除,并使结点p保存结点q存储的数据,从而变相实现删除指定结点的操作。但是如果指定结点p没有后继结点,这么做会报错
// 删除指定结点p
bool DeleteNode(LNode *p){   
    if(p==NULL)           
        return false;     
    LNode *q = p->next; // 令q指向p的后继结点  
    // 如果p是最后一个结点,则q指向NULL,继续执行就会报错  
    p->data = q->data;  
    p->next = q->next;   
    free(q);    
    return true;
}

时间复杂度:O ( 1 )

2.3.5. 单链表的查找

  1. 按位查找
typedef struct LNode{  
    ElemType data;    
    struct LNode *next;
}LNode, *LinkList;

// 查找指定位序i的结点并返回
LNode * GetElem(LinkList L, int i){   
    if(i<0)            
        return NULL;   
    LNode *p;     
    int j=0;     
    p = L;      
    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;  
    // 找到第i-1个元素     
    LNode *p = GetElem(L, i-1);   
    // 在p结点后插入元素e     
    return InsertNextNode(p, e)
}

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

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

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

  1. 计算单链表长度:
// 计算单链表的长度
int Length(LinkList L){      
    int len=0;       //统计表长  
    LNode *p = L;
    while(p->next != NULL){ 
        p = p->next;      
        len++;       
    }      
    return len;
}

时间复杂度:O ( n )

2.3.6. 单链表的建立

  1. 尾插法建立单链表:
// 使用尾插法建立单链表L
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;
}

时间复杂度:O(n)

  1. 头插法建立单链表:
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;
}

头插法实现链表的逆置:

// 将链表L中的数据逆置并返回
LNode *Inverse(LNode *L){	
    LNode *p, *q;	  
    p = L->next;     //p指针指向第一个结点	  
    L->next = NULL;  //头结点置空       
    // 依次判断p结点中的数据并采用头插法插到L链表中	
    while (p != NULL){		   
        q = p;		  
        p = p->next;	
        q->next = L->next;  
        L->next = q;	
    }	   
    return L;
}

2.3.7. 双链表

在这里插入图片描述

  1. 双链表的定义:双链表也是链表的一种。双链表的每个数据节点中都有两个指针,分别指向前驱节点和后继结点。
  2. 双链表的实现:
typedef struct DNode{            //定义双链表结点类型 
    ElemType data;               //数据域    
    struct DNode *prior, *next;  //前驱和后继指针
}DNode, *DLinklist;
  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;       
    InitDLinkList(L);       
    ...
}

// 判断双链表是否为空
bool Empty(DLinklist L){   
    if(L->next == NULL)   
        return true;      
    else             
        return false;
}
  1. 双链表的后插操作:
typedef struct DNode{     
    ElemType data;       
    struct DNode *prior, *next;
}DNode, *DLinklist;

// 将结点s插入到结点p之后
bool InsertNextDNode(DNode *p, DNode *s){ 
    if(p==NULL || s==NULL)  
        return false;         
    s->next = p->next;      
    // 判断结点p之后是否有后继结点  
    if (p->next != NULL)   
        p->next->prior = s; 
    s->prior = p;   
    p->next = s;     
    return true;
}

双链表的前插操作、按位序插入操作都可以转换成后插操作

  1. 双链表的删除操作:
// 删除p结点的后继结点
bool DeletNextDNode(DNode *p){   
    if(p==NULL)           
        return false;   
    // 找到p的后继结点q    
    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;
}

// 销毁一个双链表
bool DestoryList(DLinklist &L){ 
    // 循环释放各个数据结点   
    while(L->next != NULL){    
        DeletNextDNode(L);      
        free(L);        
        // 头指针置空  
        L=NULL;     
    }
}

  1. 双链表的遍历:
// 删除p结点的后继结点
bool DeletNextDNode(DNode *p){   
    if(p==NULL)           
        return false;   
    // 找到p的后继结点q    
    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;
}

// 销毁一个双链表
bool DestoryList(DLinklist &L){ 
    // 循环释放各个数据结点   
    while(L->next != NULL){    
        DeletNextDNode(L);      
        free(L);        
        // 头指针置空  
        L=NULL;     
    }
}

双链表不可随机存取,按位查找、按值查找操作都只能用遍历的方式实现。

2.3.8 循环链表

在这里插入图片描述

  1. 循环链表的定义: 循环链表是另一种形式的链式存储结构。它的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。
  2. 循环单链表的实现:
typedef struct LNode{           
    ElemType data;                  
    struct LNode *next; 
}DNode, *Linklist;

// 初始化循环单链表
bool InitList(LinkList &L){    
    L = (LNode *)malloc(sizeof(LNode));  
    if(L==NULL)             
        return false;    
    // 最后一个结点的next指针指向头结点    
    L->next = L;       
    return true;
}

// 判断循环单链表是否为空
bool Empty(LinkList 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;
}

  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;    
    // 头结点的prior指针指向最后一个结点,最后一个结点的next指针指向头结点 
    L->prior = L;      
    L->next = L;
}

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

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

  1. 循环双链表的插入和删除操作:
// 将结点s插入到结点p之后
bool InsertNextDNode(DNode *p, DNode *s){  
    s->next = p->next;   
    //循环双链表不用担心p结点的下一个结点为空   
    p->next->prior = s;  
    s->prior = p;     
    p->next = s;
}

// 删除p结点的后继结点
bool DeletNextDNode(DNode *p){  
    // 找到p的后继结点q       
    DNode *q =p->next;        
    //循环双链表不用担心q结点的下一个结点为空  
    p->next = q->next;    
    q->next->prior=p;    
    free(q);      
    return true;
}

2.3.9. 静态链表

  1. 静态链表的定义:用数组的方式实现的链表。分配一整片连续的内存空间,各个结点集中安置,每个结点包括了数据元素和下一个结点的数组下标。
    在这里插入图片描述

  2. 特点

    • 优点:增、删操作不需要大量移动元素。
    • 缺点:不能随机存取,只能从头结点开始依次往后查找,容量固定不变!
  3. 静态链表的定义:

#define MaxSize 10        //静态链表的最大长度
struct Node{              //静态链表结构类型的定义  
    ElemType data;        //存储数据元素    
    int next;             //下一个元素的数组下标
};

// 用数组定义多个连续存放的结点
void testSLinkList(){    
    struct Node a[MaxSize];  //数组a作为静态链表, 每一个数组元素的类型都是struct Node    
    ...
}

也可以这么定义:

#define MaxSize 10        //静态链表的最大长度
typedef struct{           //静态链表结构类型的定义       
    ELemType data;        //存储数据元素     
    int next;             //下一个元素的数组下标
}SLinkList[MaxSize];

void testSLinkList(){      
    SLinkList a;
}

第一种是我们更加熟悉的写法,第二种写法则更加侧重于强调 a 是一个静态链表而非数组。

  1. 静态链表的注意点

    • 初始化静态链表时,需要把a[0]的next设为-1,并将空闲结点的next设置为某个特殊值,比如-2。
    • 按位序查找结点时,从头结点出发挨个往后遍历结点,时间复杂度 O = ( n ) O=(n)O=(n)。
    • 按位序插入结点的步骤:①找到一个空的结点,存入数据元素;②从头结点出发找到位序为 i-1 的结点;③修 改新结点的next 为 -1;④修改 i-1 号结点的next为新结点的下标;

2.3.10. 顺序表和链表的比较

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

  2. 存储结构:

    • 顺序表:顺序存储
      优点:支持随机存取,存储密度高。
      缺点:大片连续空间分配不方便,改变容量不方便。

    • 链表:链式存储
      优点:离散的小空间分配方便,改变容量方便。
      缺点:不可随机存取,存储密度低。

  3. 基本操作 - 创建

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

    • 顺序表:修改 Length = 0
      静态分配:静态数组——系统自动回收空间。
      动态分配:动态数组——需要手动free()。
    • 链表:依次删除各个结点 free()。
  5. 基本操作 - 增/删:

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

    • 顺序表
      • 按位查找:O ( 1 ) O(1)O(1)
      • 按值查找:O ( n ) O(n)O(n),若表内元素有序,可在 O ( l o g 2 n ) O(log2n)O(log2n) 时间内找到(二分法)
    • 链表:
      • 按位查找:O ( n ) O(n)O(n)
      • 按值查找:O ( n ) O(n)O(n)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1511346.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VB 数据质量诊断软件(分析数据的完整性,合理性,准确性)-139-(代码+程序说明)

转载地址http://www.3q2008.com/soft/search.asp?keyword139 前言: 为何口出狂言,作任何VB和ASP的系统, 这个就是很好的一个证明 :) 又有些狂了... 数据库操作谁都会,接触的多了也没什么难的,VB编程难在哪?算法上,这个是一个算法题的毕业设计 哈哈忙活了足足有一○小时, …

2024年最新阿里云和腾讯云云服务器价格租用对比

2024年阿里云服务器和腾讯云服务器价格战已经打响&#xff0c;阿里云服务器优惠61元一年起&#xff0c;腾讯云服务器61元一年&#xff0c;2核2G3M、2核4G、4核8G、4核16G、8核16G、16核32G、16核64G等配置价格对比&#xff0c;阿腾云atengyun.com整理阿里云和腾讯云服务器详细配…

C语言 ——关键字

关键字&#xff1a;在C语言中被赋予了特定含义的英文单词&#xff0c;一共有32个关键字 * 关键字全部小写 * 在特定的编译器中&#xff0c;关键字是高亮显示的 vs&#xff1a;蓝色或者紫色 vs&#xff1a;蓝色 下图圈起来的都是关键字 c auto break case char const con…

Kafka消费者重平衡

「&#xff08;重平衡&#xff09;Rebalance本质上是一种协议&#xff0c;规定了一个Consumer Group下的所有Consumer如何达成一致&#xff0c;来分配订阅Topic的每个分区」。 比如某个Group下有20个Consumer实例&#xff0c;它订阅了一个具有100个分区的Topic。 正常情况下&…

【C语言】如何规避野指针

✨✨ 欢迎大家来到莉莉的博文✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 目录 一、概念&#xff1a; 二、野指针成因&#xff1a; 2.1. 指针未初始化 2.2 指针越界访问 3. 指针指向的空间释放 三、如何规避野指针 3.…

MT6771 android13 自定义背光曲线

一. Android系统源码中的参数配置 MTK6771平台MTK重写了背光曲线的参数&#xff0c;路径在s0_vnd/vendor/mediatek/proprietary/packages/overlay/vendor/FrameworkResOverlayExt/brightness_adaptive_support/res/values/config.xml 不过MTK的其他平台可能不是在这个路径 来看…

C/C++语言学习基础版(一)

目录 一and二、C语言说明 注释&#xff1a; 1、声明语句 2、输出函数 3、return 语句 三、C语言的数据结构 1、常量与变量 2、基本数据结构 3、关键字 练习&#xff1a;进制转换 四、基本输入输出 1、字符输出函数putchar 2、字符输入函数getchar 3、格式化输出函…

JavaParser的快速介绍

开发的工作主要是写代码&#xff0c; 有考虑过使用代码写代码&#xff0c; 使用代码分析和改进代码吗&#xff1f; JavaParser 就可以帮你用来处理Java 代码的这些功能。 Java Parser 的介绍 Java Parser是一个用于解析和分析Java源代码的开源工具。它提供了一个API接口&…

VMware ESXi 6.7.0 开启SNMP服务

VMware ESXi 6.7.0 开启SNMP服务&#xff0c;操作步骤如下&#xff1a; 1、用root账户&#xff0c;登录VMware ESXi 6.7.0 2、开启SSH服务 Host->Actions->Services->Enable Secure Shell(SSH) 主机->操作->服务->启用安全Shell(SSH) 3、登录SSH&#xff…

抽象工厂模式——创建型模式

抽象工厂模式——创建型模式 抽象工厂模式是一种软件设计模式&#xff0c;它解决了在创建一组相关或相互依赖的对象时的一些核心问题。其核心问题包括&#xff1a; 对象的创建与使用分离&#xff1a; 抽象工厂模式通过引入抽象工厂接口以及具体工厂类&#xff0c;将对象的创建与…

如何在Linux使用docker安装Plik并实现无公网ip上传下载内网存储的文件资源

文章目录 1. Docker部署Plik2. 本地访问Plik3. Linux安装Cpolar4. 配置Plik公网地址5. 远程访问Plik6. 固定Plik公网地址7. 固定地址访问Plik 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的 人工智能学习网站&#xff0c; 通俗易懂&#xff0c;风趣幽默&…

python学习1:csv模块、time模块、random、jieba、worldcloud、pycharm的虚拟环境认识、black格式化文件

标准库与第三方库 模块&#xff08;modules&#xff09;&#xff1a;是包含python函数和变量的文件&#xff0c;名称符合Python标识符要求&#xff0c;并使用.py后缀 包&#xff08;package&#xff09;&#xff1a;是包含其他模块、包的文件夹。名称符合Python标识符要求&am…

第三百九十八回

文章目录 1. 概念介绍2. 使用方法3. 示例代码 我们在上一章回中介绍了通道相关的内容&#xff0c;本章回中将介绍StreamProvider组件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 在Flutter中Stream是经常使用的组件&#xff0c;对该组件的监听可void main()…

【C++】STL(五) Stack Queue容器

5、 stack容器 5.1 简介 ① stack是一种先进后出的容器&#xff0c;它只有一个出口。 ② 栈中只有顶端的元素才可以被外界使用&#xff0c;因此栈不允许有遍历行为。 ③ 栈中进入数据称为&#xff1a;入栈 push ④ 栈中弹出数据称为&#xff1a;出栈 pop 5.2 常用接口 …

ansible-playbook的角色(role)

1前言 角色目录如下&#xff08;分别为httpd角色和nginx角色&#xff09; handlers/ &#xff1a;至少应该包含一个名为 main.yml 的文件&#xff1b; 其它的文件需要在此文件中通过include 进行包含 vars/ &#xff1a;定义变量&#xff0c;至少应该包含一个名为 main.yml 的…

如何使用US Domain Center和WordPress搭建非营利组织网站的详细指南

在今天的数字化时代&#xff0c;拥有一个专业、易于管理和更新的网站对于非营利组织&#xff08;例如慈善机构、NGO等&#xff09;至关重要。WordPress是一个功能强大且易于使用的网站构建平台&#xff0c;而美国域名中心 US Domain Center&#xff1a;US Domain Center 则是一…

Ansys Zemax | 如何获取公差报告中的任何优化操作数值

附件下载 联系工作人员获取附件 此文为上一篇文章“如何执行非序列公差分析”’的文末提到的详细介绍。 本文介绍如何使用 OpticStudio 的公差脚本功能在公差报告中包含任何优化操作数值&#xff0c;以及如何编写简单的公差脚本。 简介 在对系统进行公差处理时&#xff0c;…

C语言数据结构之堆排序

青衿之志 履践致远 堆排序(Heapsort) 是指利用 堆 这种数据结构所设计的一种排序算法&#xff0c;它是 选择排序 的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆&#xff0c;排降序建小堆。 &#x1f3a5;二叉堆 &#x1f3a5;二叉树 &#x1f525;期待小伙伴们…

GIS瓦片3-WMTS瓦片

介绍 WMTS( Web Map Tile Service)切片地图Web服务&#xff08;OpenGIS Web Map Tile Service&#xff09;当前最新版本是1.0.0。WMTS标准定义了一些操作&#xff0c;这些操作允许用户访问切片地图。WMTS可能是OGC首个支持RESTful访问的服务标准。 WMTS提供了一种采用预定义图…

​LeetCode解法汇总1261. 在受污染的二叉树中查找元素

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给出一个满足下述规则的二叉树&#xff1…