【408 数据结构】第2章 线性表

文章目录

线性表

考纲

  • 线性表基本概念和实现
  • 顺序存储和链式存储
  • 线性表的应用

线性表的定义和基本操作

1. 定义

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

n为表长 ,值为0表示空表 。第一个元素和最后一个元素叫表头元素表尾元素

元素有且只有一个前驱和后继,表头元素无前驱,表尾元素无后继。

线性表特点

  • 元素个数有限
  • 元素逻辑上有顺序性
  • 元素都是数据元素
  • 元素的数据类型相同,占用存储空间大小相同
  • 元素具有抽象性,不考虑元素所表示的内容,只讨论元素间的逻辑关系。

线性表、顺序表和链表的比较:

​ 线性表表示的时一种逻辑结构,顺序表和链表表示的是存储结构

2. 线性表的基本操作

c 复制代码
InitList(&L)       //初始化表,建立一个空表
Length(L)          //表长
LocateElem(L,e)    //按值查找,e是值
GetElem(L,i)       //按位查找,i是位置
ListInsert(&L,i,e)  //将e插入到L表的i位置
ListDelete(&L,i,e)  //删除L表中i位置的元素e  
PrintList(L)        //按顺序输出表L的元素
Empty(L)            //判空,若L为空则返回True    
DestroyList(&L)     //销毁L表,释放内存

线性表的顺序表示

1. 顺序表的定义

顺序表:按顺序存储的线性表。任意一个数据元素都可以实现随机存取

特点:表中元素的逻辑顺序和存储的物理顺序相同。

位序:元素在顺序表的位置。

线性表中元素的位序是从1开始的,数组中元素的下标是从0开始的。

一维数组静态分配,大小和空间事先固定,空间占满会溢出,程序崩溃。

c 复制代码
//静态分配的顺序表 存储结构 描述
#define MaxSize 50             //顺序表最大长度
typedef struct{
    ElemType data[MaxSize];    //元素,ElemType是数据类型
    int Length;                //顺序表的当前长度
}SqLst;                        //顺序表的类型定义

一维数组动态分配时,一旦数据空间占满,另开辟更大的存储空间,将原来表中的元素拷贝到新空间。

c 复制代码
//静态分配的顺序表 存储结构 描述

#define InitSize 100                //表长度的初始定义
typedef struct{
    ElemType *data;                //指示动态分配数组的指针
    int MaxSize,Length;            //数组的最大容量和当前个数
}SeqList;                          //动态分配 顺序表的类型定义

L.data = (ElemType*)malloc(sizeof(ElemType)*InitSize);     //c初始动态分配语句
L.data = new ElemType[InitSize];                           //c++初始动态分配语句

动态分配不是链式存储,是顺序存储!!!

注意看这两段代码的不同之处!

malloc(memory allocation的缩写)方法用于动态分配具有指定大小的单个大块内存。它返回void类型的指针,其指针类型可以灵活转换,同时使用默认garbage value(内存中存储的随机值)初始化内存块。其语法格式为:

ptr = (指针转换格式*) malloc(分配的字节数)

顺序表的优点:随机访问、存储密度高。

顺序表的缺点:元素的插入和删除需要移动大量元素;需要连续的存储空间。

2. 顺序表基本操作的实现

只描述 初始化、插入、删除、按值查找 这四个操作。

初始化

动态分配和静态分配的初始化操作有所不同。

之前学习到,初始化操作的代码是:InitList(&L);

静态分配的顺序表,分配的空间固定,初始化只需要长度设置为0

c 复制代码
//SqList L
void InitList(SqList &L){
    L.Length = 0;
}

动态分配的顺序表要分配存储空间,定义长度为0,设置最大容量。

c 复制代码
void InitList(SeqList &L){
    L.data = (ElemType *)malloc(InitSize*sizeof(ElemType));
    L.Length = 0;
    L.MaxSize = InitSize;
}
插入-时间复杂度O(n)

在顺序表L的第i个位置上插入元素e

若i不合法,返回false;i合法则第i个元素及其后面的元素全部后移1位,插入元素,L长度加一,返回true。

c 复制代码
bool ListInsert(SqList &L,int i,ElemType e){
    if(i<1 | i>L.Length+1)
        return false;
    if(L.length >= MaxSize)
        return false;
    for(j=L.Length;j>i;j--)
        L.data[j] = L.data[j-1];   //从最后一个元素开始后移,不理解就在纸上写一个数组,后移观察下标变化
    L.data[i-1] = e;
    L.length++;
    return true;
}
删除-时间复杂度O(n)

在顺序表L的第i个位置上删除元素e,和插入异曲同工。插入操作元素是后移,删除操作元素是前移。

c 复制代码
bool ListInsert(SqList &L,int i,ElemType e){
    if(i<1 | i>L.Length+1)
        return false;
    L.data[i-1] = e;        //将删除的元素赋值
    for(j=i;j<L.length;j++)
        L.data[j-1] = L.data[j];   //从第i个元素开始前移
    L.Length--;
    return true;
    
    
}
按值查找-时间复杂度O(n)

在顺序表中查找元素值等于e的元素,返回位序i。

c 复制代码
int LocateElem(SqList &L,ElemType e){
    int i;   //定义位序
    
    for(i=0,i<L.Length;i++)
        if(L.data[i] = e)
            return i+1;
    return 0;   //退出循环,说明查找失败
}

加油加油,学习过半~~

线性表的链式表示

1. 单链表的定义

单链表:线性表的链式存储。不可随机存取

单链表的结构:data为数据域,存放数据元素;next为指针域,存放后继结点的地址。

单链表结点类型的描述如下:

c 复制代码
typedef struct DNode{
    ElemType data;   //数据域
    struct LNode *next;  //指针域
}LNode, *LinkList;

使用头指针L来标识一个单链表,指出链表的起始地址,头指针为NULL标识一个空表。

头结点一般不携带信息。

通常单链表附加一个头结点,头指针L指向头结点。不带头结点的话,指针L指向第一个数据结点。

引入头结点的两个优点:

1.无需对第一个数据结点进行特殊处理

2.无论链表是否为空,头指针都指向头结点的非空指针(空表中的头结点的指针域为空),因此空表和非空表的处理得到了统一。

2. 单链表基本操作的实现

无特殊说明,本节默认携带头结点

单链表的初始化

带头结点:创建头结点,头指针指向头结点,头结点next域初始化为NULL。

c 复制代码
bool InitList(LinkList &L){
    L = (LNode*)malloc(sizeof(LNode));   //创建头结点
    L->next = NULL;
    return true;
}

不带头结点:头指针初始化为NULL

c 复制代码
bool InitList(LinkList &L){
    L = NULL;   //头指针初始化为NULL
    return true;
}
求表长 O(n)

计算单链表中数据结点的个数。

设置一个计数变量len,访问一个结点便+1,直到访问到空结点。

c 复制代码
int length(LinkList L){
    int len = 0;   //计数变量
    LNode *p = L;
    while(P->next!=NULL){
        p = p->next;
        len++;
    }
    return len;
}

单链表的长度不包含头结点!!

按序号查找结点 O(n)

从第一个结点开始,沿着next域从前往后寻找,直到找到第i个结点,返回该结点的指针。

c 复制代码
LNode *GetElem(LinkList L,int i){
    LNode *p = L;  //p指向第一个结点
    int j = 0;
    while(p!NULL && j<i){
        p=p->next;   //指针向后移动
        j++;
    }
    return p;
}
按值查找结点 O(n)

从第一个结点开始,依次比较各结点的数据域,当某结点的data域等于要查找的e值,则返回该指针

c 复制代码
LNode *LocateElem(LinkList L,ElemType e){
    LNode *p = L->next;
    while(p->data!=e && p!=NULL){   //不满足条件,指针就后移
        p=p->next;
    }
    return p;
}
插入结点 O(n)

将值为x的结点插入到单链表的第i个位置。

①和②的顺序一定不能反,不然会丢失后面的数据,操作就失败了

c 复制代码
bool ListInsert(LinkList &L,int i,ElemType e){
    LNode *p = L;  //p指向第一个结点
    int j = 0;   //记录当前结点的位序
    while(p!=NULL && j<i-1){   //j小于i-1时指针就后移,直到找到第i-1个结点
        p=p->next;
        j++;
    }
    if(p==NULL)    
        return false;
    LNode *s = (LNode*)malloc(sizeof(LNode));   //创建s
    s->data = e;          //data数据域值为e
    s->next = p->next;    //①
    p->next = s;          //②
    return true;
}

上述插入为 后插

前插:在某结点前插入一个元素

前插可以转化为后插操作来实现:就是正常进行后插操作后,前后两个结点的data互换一下,这样在逻辑上也就实现了效果。

c 复制代码
s->next = p->next;
p->next = s;
temp = p->data;    //借助temp作为容器,暂放数据
p->data = s->data;
s->data = temp;
删除结点 O(n)

删除单链表的第i个节点

c 复制代码
bool ListDelete(LinkList &L,int i,ElemType &e){
    LNode *p = L;   //p指向头结点
    int j = 0;   //计数
    while(p!=NULL&&j<i-1){
        p=p->next;
        j++;
    }
    while(p==NULL)
        return false;
    LNode *q = p->next;    //q指向p的下一个结点
    q->data = e;   //记录一下被删除的结点
    p->next = q->next;
    free(q);     //释放内存
    return true;
}
头插法建立单链表 O(n)
c 复制代码
LinkList List_HeadInsert(LinkList &L){
    LNode *p;int x;    //设置元素类型
    L = (LNode *)malloc(sizeof(LNode));   //创建头结点
    L->next = NULL;    //初始化为空表
    scanf("%d",&x);     //输入结点的值
    while(x!==9999){
        p = (LNode*)malloc(sizeof(LNode));  //创建新结点
        p->data = x;
        p->next = L->next;  //①
        L->next = p;    //②
        scanf("%d",&x);
    }
    return L;
}

读入数据的顺序和生成的链表的元素的顺序是相反的!,可以用来实现链表的逆置

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

增加一个尾指针r,始终指向单链表的最后一个元素。

c 复制代码
LinkList List_TailInsert(LinkList &L){
    int x;
    L = (LNode *)malloc(sizeof(LNode));   //创建头结点
    LNode *s,*r = L;    //r为尾指针,指向L
    scanf("%d",&x);     //输入结点的值
    while(x!==9999){
        s = (LNode*)malloc(sizeof(LNode));  //创建新结点
        s->data = x;   //赋值
        r->next = s;    
        r=s;        //指针后移
        scanf("%d",&x);
    }
    r->next = NULL;   //尾指针的next域置空
    return L;
}

3. 双链表

单链表结点中只要一个指向其后继的指针,使得单链表只能从前往后依次遍历。

双链表中有两个指针,分别为priornext,分别指向直接前驱直接后继

双链表的数据结构描述如下:

c 复制代码
typedef struct DNode{
    ElemType data;
    struct DNode *prior,*next;
}DNode,*DLinkList;

双链表的按值查找和按位查找和单链表相同,为O(n)

插入删除操作的时间复杂度为O(1)

双链表的插入

主要操作代码如下:

c 复制代码
① s->next = p->next;
p->next->prior = s;
s->prior = p;
④ p->next = s;

插入的语句不唯一,但是①必须要在④的前面!!!

双链表的删除
c 复制代码
p->next = q->next;
q->next->prior = p;
free(q);

4. 循环链表

循环单链表

其和单链表的区别 :最后一个结点的next不是NULL,而是指向头结点,从而形成一个环。

循环单链表的插入和删除算法和单链表几乎一样,但是操作若是在表尾进行,循环单链表不需要判断是否处于表尾。

循环单链表可以从表中的任意位置开始遍历整个单链表。

循环双链表

在循环双链表中,头结点的prior指针还要指向表尾结点。

5. 静态链表

静态链表是用数组 来描述线性表的链式存储结构,结点也有datanext

但是这里的指针指的是 结点在数组中的相对地址,称为游标。如下图,a的next存储的是1,对应的是b,b的next存储的是6,对应的是c。

静态链表需要一块连续的存储空间。

图有点黑因为宿舍关灯了hhh

静态链表的结构类型的描述如下:

c 复制代码
#define MaxSize 50;
typedef struct {
    ElemType data;
    int next;
}SLinkList[MaxSize];

小结

基础知识部分完成

后续将 课后题 拉出来详解

多敲代码,掌握基础结构的书写!!!

持续更新~~

相关推荐
搬砖的小码农_Sky2 小时前
C语言:数组
c语言·数据结构
先鱼鲨生4 小时前
数据结构——栈、队列
数据结构
一念之坤4 小时前
零基础学Python之数据结构 -- 01篇
数据结构·python
IT 青年4 小时前
数据结构 (1)基本概念和术语
数据结构·算法
熬夜学编程的小王4 小时前
【初阶数据结构篇】双向链表的实现(赋源码)
数据结构·c++·链表·双向链表
liujjjiyun5 小时前
小R的随机播放顺序
数据结构·c++·算法
Reese_Cool6 小时前
【数据结构与算法】排序
java·c语言·开发语言·数据结构·c++·算法·排序算法
djk88887 小时前
.net将List<实体1>的数据转到List<实体2>
数据结构·list·.net
搬砖的小码农_Sky7 小时前
C语言:结构体
c语言·数据结构
_OLi_9 小时前
力扣 LeetCode 106. 从中序与后序遍历序列构造二叉树(Day9:二叉树)
数据结构·算法·leetcode