C语言数据结构(一)——顺序表的操作与使用

顺序表的操作与使用

作者:竹影


一、线性表ADT

ADT List{
    数据对象:
    D = {a[i] | a[i]∈ ElemSet, i = 1,2,...,n,n >= 0}
    数据关系:
    S = {<a[i-1],a[i]> | a[i-1],a[i]∈ D,i= 1,2,...n}
    基本操作:
        (1)InitList(&L)   //初始化表格
        (2)DestoryList(&L) //销毁表格
        (3)ClearList(&L)   //清空表格
        (4)ListEmpty(L)    //判空表格
        (5)ListLength(L)   //求表格长度
        (6)GetElem(L,i,&e) //取某一位的元素
        (7)LocateElem(L,e,compare())
        //查找元素定位,compare是数据元素判定函数,若找到,则返回元素第一次出现的位序,若不存在则返回0
        (8)PriorElem(L,cur_e,&pre_e) //找前驱
        (9)NextElem(L,cur_e,&next_e) //找后继
        (10)ListInsert(&L,i,e)       //插入元素
        (11)ListDelete(&L,i,&e)      //删除元素
        (12)ListTraverse(L,visit())  //遍历线性表
        对表中的每个元素都做visit操作
} ADT List

以上就是我们线性表的基本图纸,现在我们来看看线性表的基本操作

二、线性表的基本操作

1.前置部分(搞定ADT中DS的部分)
#include <stdio.h>
#include <stdlib.h> //动态内存分配malloc函数的调用

#define LIST_SIZE   100              //线性表存储空间的初始分配量,单位为元素个数
#define LISTINCREMENT  10    //线性表存储空间的分配增量

/*特殊的判断符*/
#define OK 1
#define ERROR 0

typedef int ElemType;   //并非多此一举哦,如果之后发现表格存储元素需要改成别的数据类型,在这里可以快速改动
typedef int Status;     //与OK和ERROR对应,为函数类型

typedef struct{
   ElemType  *elem;                     //存储空间基址
   int       length;         		    //当前长度
   int       listsize;                  //当前分配的存储容量(以sizeof(ElemType)为单位 , 即也表示元素储存的最大个数)
} SqList;
2.顺序表基本操作
(1)顺序表初始化
//1.顺序表初始化
Status InitList_Sq(SqList &L)
{
    L.elem = (ElemType*)malloc(LIST_SIZE*sizeof(ElemType));  //动态分配内存,方便后需更改
    if(!L.elem)   //判断L.elem是否为NULL空指针,即内存是否分配失败
        return ERROR;

    L.length = 0;   //此时表为空表
    L.listsize = LIST_SIZE;  //初始内存大小记录

    return OK;
}
(2)销毁顺序表
//2.销毁顺序表
Status DestoryList_Sq(SqList &L)
{
    if(L.elem)
        free(L.elem);  //释放动态开辟的内存空间

    L.elem = NULL;  //重置指针
    L.length = 0;
    L.listsize = 0;

    return OK;
}
(3)清空顺序表
//3.清空顺序表
Status ClearList_Sq(SqList &L)
{
    L.length = 0;  //从逻辑上将表变成空表;
    return OK;
}

值得一提的是:顺序表的清空并没有对L.elem的内存空间进行清空,而是对长度进行清零,即:保留物理内存,只进行逻辑上的清空。这样,之后对线性表重新规划的时候是直接在原来的内存上进行覆盖,简化了代码逻辑

(4)顺序表判空
//4.顺序表判空
Status ListEmpty_Sq(SqList L)
{
    return(L.length == 0);
}

这里使用了极简表达,L.length == 0为逻辑语句,当其为真时值为非零,其为假时值为0,即当表为空表的时候返回0,等价于ERROR,反之等价于返回OK

(5)求顺序表长度
//5.求顺序表长度
int ListLength_Sq(SqList L)
{
    return (L.length);
}

没什么好说的,注意一下这里的类型使用的是int而非Status,这是因为返回的长度的数据类型为intStatus是我们转为函数返回逻辑OKERROR特设的数据类型

(6)取每一位置的元素
//6.取元素
Status GetElem(SqList L,int i ,ElemType &e)
{
    if(i < 1 || i > L.length)
        return ERROR;        //i的值不合法
    e = L.elem[i-1];
    return OK;
}

e = L.elem[i-1]也可以写 e = *(L.elem+i), 效果一样,但是更强调L.elem的指针特性,因为从C语言数组和指针的特点角度来讲,二者等价,不做详细说明,不懂的话补一下C语言指针与数组的基础

(7)找元素位置
//7.找元素位置
Status compare(ElemType e1,ElemType e2)
{
    return(e1 == e2);
}

int LocateElem_Sq(SqList L, ElemType e , Status compare(ElemType,ElemType))
{
    int i = 0;
    ElemType *p = L.elem;
    while((i < L.length) && !(*compare)(*p++,e))
    {
        i++;
    }
    if(i < L.length)
        return i+1;
    else
        return 0;
}

唯一值得补充的是,函数的指针调用:
函数指针作为函数参数
函数指针可以作为参数传递给其他函数,这在回调函数等场景中非常有用。
首先函数的指针是这样定义的:
返回类型 (*指针变量名)(参数列表)

(8)找元素的前驱
//8.找元素的前驱
Status PriorElem_Sq(SqList L,ElemType cur_e,ElemType &pre_e,Status (*compare)(ElemType,ElemType))
{
    for(int i = 0 ; i < L.length ; i++)
    {
        if((*compare)(L.elem[i],cur_e) && (i!=0))  //注意i != 0,因第一个元素没有前驱
        {
            pre_e = L.elem[i-1];
            return OK;
        }
    }
    return ERROR;
}
(9)找元素的后继
//9.找元素的后继
Status NextElem_Sq(SqList L,ElemType cur_e,ElemType &next_e,Status (*compare)(ElemType,ElemType))
{
    int i = 0;
    ElemType *p = L.elem;
    while((i < L.length-1) && (*compare)(*p++,cur_e))
    {
        i++;
        if((i >= 0 ) && (i < L.length-1))
        {
            next_e = L.elem[i+1];
            return OK;
        }
        else
            return ERROR;
    }
}

本来格式可以完全照着前面一个写的,但是这里用指针的思想重新写一个,两种任选一个喜欢的即可

(10)顺序表的插入
//10.顺序表的插入
Status ListInsert_Sq(SqList &L, int i , ElemType e)
{
    if(i < 1 || i > L.length+1)  //保证i值合法
        return ERROR;
    
    if(L.length >= L.listsize)  //当前储存空间已满
    {
        ElemType *newbase = (ElemType*)realloc(L.elem,(L.listsize + LISTINCREMENT)*sizeof(ElemType));//重新分配内存空间
        if(!newbase)
            return ERROR;
        L.elem = newbase; //刷新
        L.listsize += LISTINCREMENT; //增加内存容量
    }
}

注意一个很有意思的新函数:realloc(原内存指针,新内存指针分配的总大小),它用于在原来的基础上增加内存或减少内存,返回值和malloc一样也为指针

(11)顺序表的删除元素
//11.顺序表的删除
Status ListDelete_Sq(SqList &L, int i , ElemType &e)
{
    if(i < 1 || (i > L.length))
        return ERROR;

    ElemType *p = L.elem+i-1;
    e = *p;
    ElemType *q = L.elem+L.length-1;
    while(p < q)
    {
        *p = *(p+1);
        p++;
    }
    L.length--;
    return OK;
}

可以试着计算一下他的时间复杂度

(12)顺序表的遍历
//12.顺序表的遍历
Status visit(ElemType e)
{
    printf("%d ",e);
    //or what else
}
Status ListTraverse(SqList L,Status(*visit)(ElemType))
{
    for(int i = 0 ; i < L.length ; i++)
    {
        if(!visit(L.elem[i-1]))
            return ERROR;
    }
}

总结:由于顺序表和数组的高度逻辑相似性,指针和数组之间的切换特性仍然可用,但是要注意动态内存分配和释放
并且,对于要对列表本体进行修改就要使用引用格式&L,单纯的查找和遍历就不需要

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值