一. 线性表

线性表

1.线性表定义

定义:是具有相同数据类型的n(n>=0)个数据元素的有限序列。

两个特点:

  1. 相同的数据类型
  2. 有限序列

2.线性表的操作

(1)基本操作

注:基本操作包含建立和销毁线性表,是对线性表的修改,因此传入参数应为线性表的指针。

  1. 结构初始化:InitList(*L)——构造一个空的线性表
  2. 销毁结构:DestroyList(*L)线性表已存在——>销毁该线性表

(2)引用型操作(6个)

注:引用型操作不对线性表进行修改,因此不用传入指向线性表的指针。

  1. 判空:ListEmpty( L )——若 L 为空表,则返回 TRUE,否则返回 FALSE
  2. 计算表长:ListLength( L )——返回 L 中元素个数
  3. 计算某元素前驱:PriorElem( L, cur_e, *pre_e )——若 cur_e 是 L 中的数据元素,则用 pre_e 返回它的前驱,否则操作失败,pre_e 无定义
  4. 计算某元素后继:NextElem( L, cur_e, *next_e )——若 cur_e 是 L 中的数据元素,则用 next_e 返回它的后继,否则操作失败,next_e 无定义
  5. 获取元素:GetElem( L, i, *e )——用 e 返回 L 中第 i 个元素的值
  6. 获取元素位置:LocateElem( L, e)——返回 L 中与 e 相等的元素的序号。若这样的元素不存在,则返回值为0

(3)加工型操作

注:加工型操作即对线性表进行修改,因此传入参数应为指向被加工的线性表的指针。

  1. 置空:ClearList( *L )——将 L 重置为空表
  2. 插入元素:ListInsert( *L, i, e )——在 L 的第 i 个元素之前插入新的元素 e,L 的长度增1
  3. 删除元素:ListDelete( *L, i, *e )——删除 L 的第 i 个元素,并用 e 返回其值,L 的长度减1。

(4)例题

例1:已知集合 A 和 B,求两个集合的并集,使 A=A∪B,且 B 不再单独存在。

我自己做的:

解:查看B中的每个元素是否在A中出现——>把B中元素插入A中——>查看B中下一个元素

for(int i = 0; i < ListLength(Lb); i++){
    GetElem(Lb,i,*e);
    if(LocateElem(La,e) == 0){//该元素在La中不存在
        ListInsert(*La,ListLength(La),e);
    }
}
DestroyList(*Lb);

答案做的:

  1. 从线性表 LB 中取出一个数据元素;
  2. 依值在线性表 LA 中进行查询;
  3. 若不存在,则将它插入到 LA 中。

重复上述三步直至 LB 为空表止。

代码如下:

ListDelete( LB, 1, e );
LocateElem( LA, e);
ListInsert( LA, n+1,e );
DestroyList( LB );

3.线性表的顺序存储——顺序表

(1)顺序表的定义

顺序表: “用一组地址连续的存储单元依次存放线性表中的数据元素”,即以“存储位置相邻”表示“位序相继的两个数据元素之间的前驱和后继的关系(有序对< ai-1,ai >)”,并以表中第一个元素的存储位置作为线性表的起始地址,称作线性表的基地址

(2)顺序表中每个元素的表示

注:因为是顺序存储,所以可以直接根据位置访问这个元素。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-53ZPb8O4-1632487738241)(C:\Polaris_LEARN\WORK-LEARNING\面经\数据结构\总结\线性表.assets\image-20210905153758071.png)]

(3)顺序表类型

const int MAXLISTSIZE = 80
typedef struct{
	ElemType *elem;//存储空间基址
	int length;//顺序表当前长度
	int listsize;//顺序表允许的最大长度
}SqList;

(3)相关操作的具体实现

1.初始化操作

对顺序表而言,“初始化”的实质是为它分配一个“足够应用”的元素存储空间。(因此要确定该顺序表的最大容量——传参时要传入)

void InitList(SqList* L, int maxsize){
    if(maxsize == 0){
		maxsize = MAXLISTSIZE;
    }
    L->elem = new ElemType[maxsize];//分配长度为maxsize的空间
    if(!L->elem)//存储分配失败
        exit(1);
    L->length = 0;
    L->listsize = maxsize;
}

时间复杂度:O(1)

【附】new的使用方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2LDi20Zs-1632487738243)(C:\Polaris_LEARN\WORK-LEARNING\面经\数据结构\总结\线性表.assets\image-20210905184041423.png)]

2.元素定位操作

在顺序表中“查询”是否存在一个和给定值满足判定条件的元素的最简单的办法是,依次取出结构中的每个元素和给定值进行比较。

int LocateElem(SqList L, ElemType e){
	int i = 1;
    ElemType* p = L.elem;
    while(i <= L.length && *(p++) != e){
        i++;
    }
    if(i <= L.length){
		return i;
    }
    else{
		return 0;
    }
}
3.插入元素操作

插入元素属于加工型操作,因此会使得顺序表的逻辑结构发生变化,因此记得对其结构内的元素进行改变。

逻辑结构的变化:

  1. 表长+1
  2. 插入元素以后的元素位置的变化
bool ListInsert(SqList* L, int pos, ElemType e){
	//插入位置不合法
    if(pos < 1 || pos > L->length + 1)
        return false;
    //顺序表的存储位置已满
    if(L->length == L->listsize)
        return false;
    //因为是顺序存储,所以在插入元素之后后面的元素要后移
    for(int j = L->length - 1; j >= pos - 1; j--){
    	L->elem[j + 1] = L->elem[j];
    }
    L->elem[pos - 1] = e;
    L->length = L->length + 1;
    return true;
}

时间复杂度为:O (ListLength(L))

4.删除元素操作

删除元素属于加工型操作,会使顺序表的逻辑结构发生变化。变化如下:

  1. 表长-1
  2. 删除元素之后的元素的位置变化
bool ListDelete(SqList* L, int pos, ElemType* e){
	//查看删除位置pos是否合法
    if(pos < 1 || pos > L->length)
        return false;
    //对删除元素之后的元素进行移位(向前移位)
    e = L->elem[pos - 1];
    for(int j = pos - 1; j <= L->length - 2; j++){
		L->elem[j] = L->elem[j + 1];   
    }
    L->length = L->length - 1;
    return true;
}

时间复杂度为:O (ListLength(L))

5.销毁结构操作
void DestroyList(SqList* L){
	delete[] L->elem;
    L->length = 0;
    L->listsize = 0;
}

时间复杂度为:O (1)

【附】delete使用方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tMDnlwmN-1632487738244)(C:\Polaris_LEARN\WORK-LEARNING\面经\数据结构\总结\线性表.assets\image-20210905184112951.png)]

【相关计算】关于插入元素和删除元素所需移动元素的平均个数

假设线性表的长度为n,在第i个位置插入或删除元素。

(1)插入元素

从第i到第n个元素需要移动,则需要移动n-i+1个元素。

对于i,可取值范围为:1~n+1。则相加可得:(n+1)2-(1+(n+1))(n+1)/2=(1/2)*n2+n+1/2-(1/2)n-1/2=(n^2+n)/2。i的取值概率是均等的,因此平均需要移动的元素个数为n/2。

(2)删除元素

不再赘述,通过计算可得平均需要移动的元素个数为(n-1)/2。

(4)顺序表存储结构的特点

  1. 逻辑上相邻的元素,其物理位置也相邻
  2. 可随机存取表中任一元素
  3. 必须按最大可能长度预分存储空间,存储空间利用率低,表的容量难以扩充,是一种静态存储结构
  4. 插入删除时,需移动大量元素,平均移动元素为n/2

4.线性表的链式存储——链表

(1)链表的定义

线性表的链式存储表示的特点是用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)

为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,数据元素ai除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-An5xTZbB-1632487738246)(C:\Polaris_LEARN\WORK-LEARNING\面经\数据结构\总结\线性表.assets\image-20210905191603184.png)]

每个节点由两部分组成:数据域(存储数据元素的信息)和指针域(存储指向下一节点的指针)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UDcabg3B-1632487738247)(C:\Polaris_LEARN\WORK-LEARNING\面经\数据结构\总结\线性表.assets\image-20210905211143554.png)]

单链表:每个结点中只包含一个指针域

头指针:第一个数据元素的存储地址为链表的基地址,指向该基地址的指针就是头指针

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wNFJSJye-1632487738248)(C:\Polaris_LEARN\WORK-LEARNING\面经\数据结构\总结\线性表.assets\image-20210905211934949.png)]

带“头结点”的单链表:在第一个结点之前附加一个“头结点”,令该结点中指针域的指针指向第一个元素结点,并令头指针指向头结点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tMlGS5zL-1632487738249)(C:\Polaris_LEARN\WORK-LEARNING\面经\数据结构\总结\线性表.assets\image-20210905212424948.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AikqF9qN-1632487738249)(C:\Polaris_LEARN\WORK-LEARNING\面经\数据结构\总结\线性表.assets\image-20210905213544966.png)]

(2)链表类型

struct node{
	ElemType data;
    struct node* next;
}
typedef struct node LNode;

(3)相关操作的具体实现

1.初始化操作

初始化操作需要做什么:建立一个空的链表

根据链表是否带头节点,由不同的初始化操作。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MMmOEYS4-1632487738250)(C:\Polaris_LEARN\WORK-LEARNING\面经\数据结构\总结\线性表.assets\image-20210905214333550.png)]

代码实现:(假设要初始化一个带头结点的单链表)

typedef LNode* LinkList;
void InitList(LinkList L){
    //L即为指向头结点的头指针
    L = new LNode;
    if(!L)
        exit(1);
    L->next = NULL;
}

时间复杂度为O (1)

2.销毁结构操作
void DestroyList(LinkList L){//L为指向头结点的头指针
	//需要销毁每一个结点:从前向后销毁
    LinkList tmp;//临时结点
    while(L){
	    tmp = L;//临时结点
        L = L->next;
        delete tmp; 
    }
}

算法的时间复杂度为O (Listlength(L))

3.链表的遍历
bool roam(LinkList L){
    //需要一个临时指针来完成遍历(不能修改指针L的值,因此需要一个临时指针)
	LinkList tmp = L;
    while(tmp){
		tmp = tmp->next;
    }
    return true;
}
4.求表长
int ListLength(LinkList L){
	int length = 0;
    LinkList tmp = L;
    while(tmp){
		tmp = tmp->next;
        length++;
    }
    return length;
}
5. 按值查找
LinkList LocateElem(LinkList L, ElemType e){
	LinkList tmp = L;
    while(tmp!=NULL && tmp->data != e){
		tmp = tmp->next;
    }
    if(tmp != NULL)
        return tmp;
}
6.按位置查找
bool GetElem(LinkList L, int pos, ElemType* e){
	//判断pos的合法性,若不合法便直接返回false
    if(pos < 1 || pos > ListLength(L)){
		return false;
    }
     LinkList tmp = L->next;//假设该链表是带有头结点的单链表
    int i = 1;
    while(tmp != NULL && i != pos){
		tmp = tmp->next;
        i++;
    }
    if(tmp == NULL)
        return false;
    else{
        e = &(tmp->data);
		return true;
    }
}
7.插入元素操作
bool ListInsert(LinkList L, int pos, ElemType e){
	//我们仍然假设该链表带有头结点
    //首先判断pos的合法性
    if(pos < 1 || pos > ListLength(L))
        return false;
    //确定待插入位置的前一个位置
    int i = 0;
    LinkList tmp1 = L;
    while(tmp1 && i < pos - 1){
		tmp1 = tmp1->next;
        i++;
    }
    //为被插入元素创建一个结点tmp2
    LinkList tmp2 = new LNode;
    if(!tmp2)
        exit(1);
    tmp2->data = e;
    tmp2->next = NULL;
    //插入过程
    LinkList tmp3 = tmp1->next;
    tmp1->next = tmp2;
    tmp2->next = tmp3;
    return true;
}
8.删除元素操作
bool ListDelete(LinkList L, int pos, ElemType e){
    //判断位置的合法性
	if(pos < 1 || pos > ListLength(L))
        return false;
    //确定待删除元素的前一个位置
    int i = 0;
    LinkList tmp1 = L;
    while(i < pos - 1){
        tmp1 = tmp1->next;
        i++;
    }
    //确定待删除元素tmp2+修改指针+删除
    LinkList tmp2 = tmp1->next;
    tmp1->next = tmp2->next;
    e = tmp2->data;
    delete tmp2;
    return true;
}

(4)例题

例1.逆序创建链表

题目要求:假设线性表( a1 ,a2 ,…, an )的数据元素存储在一维数组 A[n]中,则从数组的最后一个分量起,依次生成结点,并逐个插入到一个初始为“空”的链表中。

解题代码如下:

LinkList reverse(ElemType A[], int n){
	//初始化一个空链表,其中L为指向头结点的头指针
    LinkList L = new LNode;
    if(!L)
        exit(1);
    L->next = NULL;
    //逆序插入空链表:把每一个数据元素插入头结点之后便可
    for(int i = n; i > 0; i--){
		//为该数据元素创建结点
        LinkList tmp = new LNode;
        if(!tmp)
            exit(1);
        tmp->data = A[i];
        tmp->next = NULL;
        //把该结点插入头结点之后
        tmp->next = L->next;
        L->next = tmp;
    }
    return L;
}
例2.链表的逆置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KLpYItWp-1632487738251)(C:\Polaris_LEARN\WORK-LEARNING\面经\数据结构\总结\线性表.assets\image-20210910221906167.png)]

例3

题目要求:以链表作存储结构将线性表 ( a1 , a2,…, am , b1 , b2 ,…, bn ) 改变成 (b1,b2,…, bn , a1 , a2 ,…, am ) 。

代码如下:

void func(LinkList L, int m){
	//需要找到第m、m+n个位置
    LinkList tmp = L;
    LinkList tmp_m;
    LinkList tmp_n;
    //定位
    int i = 0;
    while(tmp->next){
		tmp = tmp->next;
        i++;
        if(i == m){
			tmp_m = tmp;
        }
    }
    tmp_n = tmp;
    //修改指针
    LinkList tmp_m1 = tmp_m->next;
    tmp_m->next = NULL;
    tmp_n->next = L->next;
    L->next = tmp_m1;
}
例4

题目:设带头结点的链表,每个节点含有三个域:data,next和sort,其中data为整数值域,next和sort均为指针域,现所有节点已经由next域链接起来,构成单链表,试编写利用sort域把所有节点按照值的从小到大的顺序链接起来的算法。(本题实质上是一个排序问题,要求“不得使用除该链表结点以外的任何链结点空间”。)

解析:链表上的排序采用直接插入排序比较方便,即首先假定第一个结点有序,然后,从第二个结点开始,依次插入到前面有序链表中,最终达到整个链表有序。

代码如下:

//链表元素结构体如下:
struct node{
	ElemType data;
    struct node* next;
    struct node* sort;
}
typedef struct node LNode;
typedef LNode* LinkList;
//排序函数(直接插入排序)如下:
LinkList LinkListSort(LinkList L){//L为指向头结点的指针
	LinkList previous = L->next;
    if(previous == NULL)
        exit(1);
    L->sort = previous;
    LinkList current = previous->next;
    while(current){
        //把current->sort插入sort中适当的位置
        LinkList tmp_pre = L->sort;
        LinkList tmp_cur = tmp_pre;
		while(tmp_cur && current->data > tmp_cur->data){     
            tmp_pre = tmp_cur;
			tmp_cur = tmp_cur->sort;
        }
        tmp_pre->sort = current;
        current->sort = tmp_cur;
        //继续下一个current的插入(修改指针)
        previous = current;
        current = current->next;
    }
    return L;
}

(5)单链表的优势和劣势

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vYHSZfnz-1632487738251)(C:\Polaris_LEARN\WORK-LEARNING\面经\数据结构\总结\线性表.assets\image-20210908192044655.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B6lKHbWr-1632487738252)(C:\Polaris_LEARN\WORK-LEARNING\面经\数据结构\总结\线性表.assets\image-20210908192110870.png)]

(6)双向链表

1.双向链表的数据结构

其结点结构中含有两个指针域,其一指向数据元素的"直接后继",另一指向数据元素的"直接前驱"

typedef struct DuLNode {
    ElemType data;
    struct DuLNode *prior;
    struct DuLNode *next;
} DuLNode, *DuLink;
2.双向循环链表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W0Dcyne9-1632487738252)(C:\Polaris_LEARN\WORK-LEARNING\面经\数据结构\总结\线性表.assets\image-20210908192842317.png)]

3.相关操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nDVUsW7s-1632487738253)(C:\Polaris_LEARN\WORK-LEARNING\面经\数据结构\总结\线性表.assets\image-20210908193250970.png)]

(7)循环链表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-opZ2j4Hu-1632487738253)(C:\Polaris_LEARN\WORK-LEARNING\面经\数据结构\总结\线性表.assets\image-20210908193142012.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wPW1kICr-1632487738254)(C:\Polaris_LEARN\WORK-LEARNING\面经\数据结构\总结\线性表.assets\image-20210908193209456.png)]

L;
}


### (5)单链表的优势和劣势

[外链图片转存中...(img-vYHSZfnz-1632487738251)]

[外链图片转存中...(img-B6lKHbWr-1632487738252)]

### (6)双向链表

#### 1.双向链表的数据结构

其结点结构中含有两个指针域,其一指向数据元素的"直接后继",另一指向数据元素的"直接前驱"

```c++
typedef struct DuLNode {
    ElemType data;
    struct DuLNode *prior;
    struct DuLNode *next;
} DuLNode, *DuLink;
2.双向循环链表

[外链图片转存中…(img-W0Dcyne9-1632487738252)]

3.相关操作

[外链图片转存中…(img-nDVUsW7s-1632487738253)]

(7)循环链表

[外链图片转存中…(img-opZ2j4Hu-1632487738253)]

[外链图片转存中…(img-wPW1kICr-1632487738254)]

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值