数据结构之线性表

数据结构之线性表(C语言)

一.线性表简述

1.定义:

线性表是具有相同数据结构类型的n(n>=0)个数据元素的有限序列,其中n为表长,当n=0时,线性表是一个空表。

注:在线性表中,出表头和表尾元素之外,每个元素有且仅有一个直接前驱和直接后继,表头元素无直接前驱,表尾元素无直接后继。

2.线性表有两类存储结构:

线性存储和链式存储。

线性存储结构:顺序表。

链式存储结构:单链表,双链表,循环链表,静态链表。其中静态链表借助数组实现,其他由指针实现。

3.线性表的基本操作

InitList(&L):初始化表。构造一个空的线性表。

Length(L):求表长。

LocateElem(L,e):按值查找操作。

GetElem(L,i):按位查找。

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

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

注:基本操作的实现取决于采用哪种存储结构,存储结构不同,算法的实现也不同;

& 表示C++中的引用调用,在C语言中采用指针效果相同。

二.顺序表

顺序存储结构,在内存空间中连续。

顺序表分为静态分配顺序表和动态分配顺序表。静态分配顺序表事先定义好顺序表的最大长度,数据溢出报错;动态分配顺序表在数据溢出时开辟新空间。

1.1顺序表的定义:
#define MaxSize 10//宏定义最大长度
typedef struct
{
    ElemType data[MaxSize];//用静态数字存放数据元素,ElemType改成自己需要的数据类型即可,eg:int
    int Length;//顺序表的长度
}SqList;//sequence

1.2初始化:
void InitList(SqList &L)
{
    for(int i =0;i<MaxSize;i++)
    {
        L.data[i]=0;//初始化。
    }
    L.length=0;//将表长置0
}
int main()
{
    SqList L;
    InitList(L);
    
    return 0;
}
2.1动态分配定义

头文件 : #include<stdlib.h>

#define InitList 10
typedef struct
{
    int *data;//指示动态分配数组的指针
    int MaxSize;//顺序表的最大容量
    int length;//顺序表的当前长度
}SqLsit;
2.2动态分配实现

使用malloc函数实现实现动态(时间开销大),用realloc也可实现,但初学者建议用malloc,realloc自学。

void InitList(SqList &L)
{
    L.data=(int *)malloc(InitSize * sizeof(int));
    //malloc函数返回值是指针,需要强制转换为自己需要的类型
    //InitList为默认增加长度,sizeof()为数据类型所占用空间
    //InitSize * sizeof(int)即分配10个int类型长度。
    L.length=0;//长度赋空
    L.MaxSize=InitSize;//初始化时,分配默认长度
}
2.3动态增加数组长度
void IncreaseSize(SqLsit &L,int len)
{
    int *p=L.data;//将L中的数据地址存到P中。
    L.data=(int *)malloc((L.MaxSize+len)*sizeof(int));
    //动态分配一个MaxSize+len的连续内存空间(重点连续)
    for(int i =0;i<L.length;i++)
    {
        L.data[i]=p[i];//放到新空间中
    }
    L.MaxSize = L.MaxSize+len;//最大空间值改变
    free(p);//将原L内存空间P释放
}
3.顺序表的插入操作
#define MaxSize 10
typedef struct
{
    int data[MaxSize];
    int Length;
}SqList;

//插入操作函数
void ListInsert(SqList &L,int i,int e)//位置i插入元素e
{
    for(int j = L.length;j>=i;j--)
    {
        L.data[j]=L.data[j-1];//注意数组内元素的起始下标为0
    }
    L.data[i-1]=e;//插入元素e
    L.length++;//长度+1
}
int main()
{
    SqList L;
    InitList(L);//初始化
    ListInsert(L,3,3);//在第三个位置插入元素3
    return 0;
}
4.顺序表的删除操作
bool ListDelete(SqList &L,int i,int &e)
{
    if(i<1||i>L.length)//保证i的有效性,此处的i为第几个,非数组内的下标1
    {
        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;//需要用函数将e的值带回来。
    if(ListDelete(L,3,e))
    {
        printf("%d",e);
    }else
    {
        printf("i不合法,删除失败");
    }
    return 0;
}
5.顺序表的按位查找操作

按值查找,只需用for循环遍历搜索即可。

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

int GetElem(SqList L,int i)//查找操作
{
    return L.data[i-1];
}

int main()
{
    SqList L;
    InitList(L);//初始化
    int x=GetElem(L,i);//查找第i位的元素
    return 0;
}
3.思考

malloc实现原理,realloc使用,进行增删改查的时间复杂度和空间复杂度。

三.链表

链表不需要使用地址连续的存储单元,不要求逻辑上相邻的元素在物理位置上也相邻。存储结构能反映数据之间的逻辑关系。解决了顺序表的插入删除困境,却失去随机存取的优点。

3.1单链表

1.定义单链表
typedef struct LNode{ 
    ElemType data;//每个结点存放一个数据元素(数据域)       
    struct LNode *next;//指针指向下一个结点(指针域)
}LNode,*LinkList;//分别表示结点和链表,其实互用也可,为了规范

/*
注:typedef <数据类型> <别名>   typedef 关键字 —— 数据类型重命名
上面代码等价: 
typedef struct LNode LNode;
typedef struct LNode *LinkList;
要表示一个单链表时,只需声明一个头指针 L ,指向单链表的第一个结点:
LNode * L;与 LinkList L; 是声明一个指向单链表第一个节点的指针。
后者代码可读性更强。
*/

LNode * p = (LNode *) malloc(sizeof(LNode));
//增加一个新的结点:在内存中申请一个结点所需空间,并用指针 p 指向这个结点
2.建立单链表

头插法建表(带头结点):

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));//此处用lnode强调这是一个结点
        s->data=x;
        s->next=L->next;
        L->next=s;
        scanf("%d",&x);
    }
    return L;
}

尾插法建表(带头结点):

每次均需要循环,时间复杂度O(n^2)

bool InitList(LinkList &L)
{
    L=(LNode *)malloc(sizeof(LNode));
    if(L==null)//无内存
        return false;
    L-next=null;
    return true;
}
bool ListInsert(LinkList &L,int i,ElemType e)//重点在于每次查找到第i-1个元素,
{
    if(i<1)
        return false;
    LNode *p;
    int j = 0;
    p=L;
    while(p!=null &&j<i-1)//每次都从头开始之后遍历,时间复杂度为 O(n^2)
    {
        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;
}
int main()
{
    LinkList L;
    InitList(L);
    
}

不带头结点的单链表

typedef struct LNode{ 
    ElemType data;       
    struct LNode *next;
}LNode,*LinkList;
//初始化链表
bool InitList(LinkList &L)
{
    L=null;//空表,无任何结点
    return true;
}
int main()
{
    LinkList L;
    InitList(L);//初始化
    ............
}

注:不带头结点,写代码更麻烦 对第一个数据结点和后续数据结点的处理需要用不同的代码逻辑 对空表和非空表的处理需要用不同的代码逻辑简言之,带头结点好

3.单链表的插入操作

按位序插入(带头结点)

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

找到第 i-1 个结点, 将新结点插入其后;头结点可以看作 “第0个”结点

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++;
    }
    if(p==null)
        return false;
    LNode *s=(LNode *)malloc(sizeof(LNode));
    s->data=e;
 1.   s->next=p->next;
 2.   p->next=s;//将结点s连接到p之后    
    //注:1与2顺序一定不能颠倒,会死循环s;
    return true;//插入成功
}

不带头结点

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;
    while(p!=null&&j<i-1)
    {
        p=p->next;
        j++;
    }
    LNode *s=(LNode *)malloc(sizeof(LNode));
    s->data=e;
    s->next=p->next;
    p->next=s;//s连接到p之后
    return true;
}

指定结点的前插操作

在p结点之前插入元素e

bool InsertPriorNode (LNode *p,ElemType e)

常规做法是循环查找p的前驱q,再对q进行后插

技巧(先进行后插,再交换元素值):

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;
    s->data=p->data;
    p->next=s;
    p->data=e;
    return true;
}
4.单链表按位序删除(带头结点)

找到第 i-1 个结点,将其指针指向第i+1个结点,并释放第i个结点。

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

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)i值不合法
        return false;
    if(p->next==null)
        return false;
    LNode *q=p->next;
    e=q->data;
    p->next=q->next;//相当于p->next=p->next->next;
    free(q);//释放结点存储空间
    return true;
}
/*
不带头结点时,删除第一个结点只需要让L=L->next;
*/

3.2双链表

在单链表的条件下增加前驱结点。

1定义:
typedef struct DNode{
    ElemType data;//数据域
    struct DNode *prior,*next;//前驱与后继结点
}DNode,*DLinkList;
2初始化
bool InitDLinkList(DLinkList &L)
{
    L = (DNode *)malloc(sizeof(DNode));//分配一个头结点
    if(L==null)
        return false;
    L-prior=null;//头结点的prior永远指向null
    L->next=null;
    return true;
}
int main()
{
    DLinkList L;
    InitDLinkList(L);
}
3.判断双链表是否为空
bool Empty(DLinkList L)
{
    if(L->next==null)
        return true;//空则返回正确
    return false;
}
4.双链表的插入操作

p结点之后插入s;

bool InsertNextDNode(DNode *p,DNode *s)
{
    s->next=p->next;
    s->prior=p;
    if(p->next!=null)//判断p是否为最后一个结点,防止空指针错误
    p->next->prior=s;//p的后继结点的前驱部分设置为s
    p->next=s;
}

注:链表的所有插入操作都可转化为后插操作。

5.双链表的删除操作

删除p结点的后继结点

bool DeleteNextDNode(DNode *p)
{
    if(p==null)
    {
        return false;
    }
    DNode *q=p->next;//p的下一个结点
    if(q==null)//p没有后继
        return false;
    p->next=q->next;
    if(q->next!=null)//判断q是否为最后一个结点
    {
        q->next->prior=p;
    }
    free(q);
    return true;
}
/*核心代码:
DNode *q = p->next;
p->next=q->next;
q->next->prior=p;
*/

3.3循环链表

1.循环单链表

在单链表的条件下将最后一个结点的后继指向头结点

从一个结点出发,可以找到其他任何一个结点

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;//后继结点指向头结点,构成循环。
    return true;
}
//判空,判断表尾结点道理一样
bool Empty(LinkList L)
{
    if(L->next ==L)
        return true;
    return false; 
}
2.循环双链表
//初始化循环双链表
bool InitDLinkList(DLinklist &L)
{
    L=(DNode *)malloc(sizeof(DNode));
    if(L==null)
        return false;
    L->prior=L;
    L->next=L;
    return true;
}
//p结点之后插入S结点
bool INsertNextDNode(DNode *p,DNode *s)
{
    s->next=p->next;
    s->prior=p;
    p->next->prior=s;
    p->next=s;
}
//删除p的后继结点q:
p->next=q->next;
q->next->prior=p;
free(q);

4.顺序表和链表的选择

需扩容:

顺序表链表
弹性(可扩容)
增、删
查询
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

录大大i

如果文章有用,还请鼓励一下作者

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值