@TOC


⏳一、线性表的定义和特点及案例引入

1.线性表的定义和特点

了解线性表内元素的关系,在此我用图把它们的关系罗列出来。

image-20211026083502403

image-20211026083658533


2.案例引入

(1) 一元多项式的运算

了解每一项的指数与其在线性表中的对应关系。

image-20211026083851865

创建新数组c,分别从头遍历比较a和b的每一项,指数相同,对应系数相加,若其和不为零,则在c中增加一个新项。指数不相同,则将指数较小的项复制到c中。当一个多项式已遍历完毕时,将另一个剩余项依次复制到c中即可。

在这里插入图片描述


(2) 稀疏多项式的运算

对于稀疏多项式,每个项的指数不再代表一个数组的下标,因此分别要开辟空间存放系数和指数。

image-20211026084044259

对于指数不是依次增加的多项式,要学会如何去存储数据。

例如:

在这里插入图片描述

因此当我们在存储上面两个多项式时,如果仍然使用顺序表进行存储,则缺点有:

(1)存储空间分配不灵活。(2)运算的空间复杂度高。我们应该使用链式结构进行存储。

image-20211026093824382

进行上面两个多项式的相加:

分析:首先我们从A的结点开始,往后一位则是指数为0,系数为7的结点,与B进行比较,发现B中没有指数为0的结点,我们不作处理,再对链表A后面的结点进行分析。

发现链表A与链表B都有指数为1的结点,则将它们的系数相加放在链表A中。以此类推进行分析。过程与结果如下图:

image-20211026084444793

image-20211026093725274

小结:
(1) 线性表中数据元素的类型可以为简单类型,也可以为复杂类型。
(2) 许多实际应用问题所涉的基本操作有很大相似性,不应为每个具体应用单独编写一个程序,过于耗费成本。
(3) 从具体应用中抽象出共性的逻辑结构和基本操作(抽象数据类型),然后实现其存储结构和基本操作。


⚾二、线性表的类型定义

线性表主要分为顺序表与链表,它们的重要基本操作有初始化、取值、查找、插入、删除。顺序表的存储方式为随机存储,链表的存储方式为顺序存储。

顺序存储定义:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构。简言之,逻辑上相邻,物理上也相邻。

顺序存储方法:用一组地址连续的存储单元依次存储线性表的元素,可通过数组arr[n]来实现。


✨三、线性表的顺序表示和实现

数据元素相互之间的存储关系

image-20211026085123267


顺序表的类型定义:

#define  MAXSIZE 100     //最大长度
typedef  struct 
{
     ElemType  *elem;      //指向数据元素的基地址
    int  length;                 //线性表的当前长度                        
 }SqList;

1.初始化顺序表

首先,我们要创建一个顺序表类型,该顺序表类型包括了顺序表的起始位置、记录顺序表内已有元素个数的计数器(size),以及记录当前顺序表的容量的变量(capacity)。

typedef int SLDataType;

typedef struct SeqList
{
    SLDataType* a;//声明了一个指向顺序表的指针,姑且称它为“顺序表指针”
    int size;//记录当前顺序表内元素个数
    int capacity;//记录当前顺序表的最大容量
}SeqList;

然后,我们需要一个初始化函数,对顺序表进行初始化。

//初始化顺序表
void SeqListInit(SeqList* ps)
{
    assert(ps);
    ps->a = NULL;//刚开始时顺序表为空,顺序表指针为NULL
    ps->size = 0;//起始时元素个数为0
    ps->capacity = 0;//容量为0
}

2.销毁顺序表

因为顺序表所用的内存空间是动态开辟在堆区的,所以我们在使用完后需要及时对其进行释放,避免造成内存泄漏

//销毁顺序表
void SeqListDestory(SeqList* ps)
{
    assert(ps);
    free(ps->a);//释放顺序表指针指向的空间
    ps->a = NULL;//及时置空
    ps->size = 0;//元素个数置0
    ps->capacity = 0;//容量置0
}

若需要对数据进行保存,可以使用文件操作函数将数据保存到一个文件中,下次使用顺序表的时候先读取文件数据即可。


3.打印顺序表

打印就比较简单了,依次循环打印size个元素即可。

//打印顺序表
void SeqListPrint(SeqList* ps)
{
    assert(ps);
    int i = 0;
    //循环打印顺序表指针指向的数据
    for (i = 0; i < ps->size; i++)
    {
        printf("%d ", ps->a[i]);
    }
    printf("\n");
}

4.扩容实现

仔细想想,我们每次需要增加数据的时候,首先都应该先检查顺序表内元素个数是否已达顺序表容量上限。若已达上限,那么我们就需要先对顺序表进行扩容,然后才能增加数据。

//检查顺序表容量是否已满,若已满,则增容
void SeqCheckCapacity(SeqList* ps)
{
    if (ps->size == ps->capacity)//满了,需要增容
    {
        //判断顺序表容量是否为0,若为0,则先开辟用于存放4个元素的空间大小,若不为0,则扩容为原来容量的两倍
        int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
        SLDataType* newA = realloc(ps->a, newcapacity*sizeof(SLDataType));
        if (newA == NULL)
        {
            printf("realloc fail\n");
            exit(-1);
        }
        ps->a = newA;//开辟成功,将顺序表指针更新
        ps->capacity = newcapacity;//容量更新
    }
}

注意:若传入realloc的指针为空指针(NULL),则realloc函数的作用相当于malloc函数。


将顺序表原有的数据从后往前依次向后挪动一位,最后再将数据插入表头。


5.头插

要想在顺序表的表头插入数据,那么就需要先将顺序表原有的数据从后往前依次向后挪动一位,最后再将数据插入表头。

//头插
void SeqListPushFront(SeqList* ps, SLDataType x)
{
    assert(ps);
    SeqCheckCapacity(ps);//检查容量
    int i = 0;
    for (i = ps->size; i > 0; i--)//将数据从后往前依次向后挪
    {
        ps->a[i] = ps->a[i - 1];
    }
    ps->a[0] = x;
    ps->size++;//顺序表元素个数加一
}

注意:挪动数据的时候应从后向前依次挪动,若从前向后挪动,会导致后一个数据被覆盖。


6.尾插

尾插相对于头插就比较简单了,直接在表尾插入数据即可。

//尾插
void SeqListPushBack(SeqList* ps, SLDataType x)
{
    assert(ps);
    SeqCheckCapacity(ps);//检查容量
    ps->a[ps->size] = x;
    ps->size++;//顺序表元素个数加一
}

7.指定下标位置插入

要做到在指定下标位置插入数据,首先我们需要得到一个下标位置,然后从该下标位置开始(包括该位置),其后的数据从后往前依次向后挪动一位,最后将数据插入到该下标位置。

//指定下标位置插入
void SeqListInsert(SeqList* ps, int pos, SLDataType x)
{
    assert(ps);
    assert(pos >= 0 && pos <= ps->size);//检查输入下标的合法性
    SeqCheckCapacity(ps);//检查容量
    int i = 0;
    for (i = ps->size; i > pos; i--)//从pos下标位置开始,其后的数据从后往前依次向后挪
    {
        ps->a[i] = ps->a[i - 1];
    }
    ps->a[pos] = x;
    ps->size++;//顺序表元素个数加一
}

我们可以发现,头插和尾插实际上就是在下标为0的位置和下标为ps->size的位置插入数据,也就意味着我们可以统一使用该函数来实现头插和尾插。

//头插
void SeqListPushFront(SeqList* ps, SLDataType x)
{
    SeqListInsert(ps, 0, x);//在下标为0的位置插入数据
}
//尾插
void SeqListPushBack(SeqList* ps, SLDataType x)
{
    SeqListInsert(ps, ps->size, x);//在下标为ps->size的位置插入数据
}

删除数据,其实可以理解为:从某个位置开始,数据依次向前覆盖。这样一来,该位置的数据就相当于删除了。

8.头删

要删除表头的数据,我们可以从下标为1的位置开始,依次将数据向前覆盖即可。

//头删
void SeqListPopFront(SeqList* ps)
{
    assert(ps);
    assert(ps->size > 0);//保证顺序表不为空
    int i = 0;
    for (i = 0; i < ps->size - 1; i++)//将数据从前往后依次向前覆盖
    {
        ps->a[i] = ps->a[i + 1];
    }
    ps->size--;//顺序表元素个数减一
}

注意:数据覆盖的时候应从前向后依次覆盖,若从后向前覆盖,会导致前一个数据被覆盖。


9. 尾删

尾删就更简单了,直接将顺序表的元素个数减一即可。

//尾删
void SeqListPopBack(SeqList* ps)
{
    assert(ps);
    assert(ps->size > 0);//保证顺序表不为空
    ps->size--;//顺序表元素个数减一
}

10.指定下标位置删除

要删除指定下标位置的数据,我们只需要从下标位置开始,其后的数据从前向后依次覆盖即可

//指定下标位置删除
void SeqListErase(SeqList* ps, int pos)
{
    assert(ps);
    assert(ps->size > 0);//保证顺序表不为空
    assert(pos >= 0 && pos < ps->size);
    int i = 0;
    for (i = pos; i < ps->size - 1; i++)//从pos下标位置开始,其后的数据从前往后依次向前覆盖
    {
        ps->a[i] = ps->a[i + 1];
    }
    ps->size--;//顺序表元素个数减一
}

同样的道理,头删和尾删实际上也就是删除下标为0的位置和下标为ps->size - 1的位置的数据,也就意味着我们可以统一使用该函数来实现头删和尾删。

//头删
void SeqListPopFront(SeqList* ps)
{
    SeqListErase(ps, 0);//删除下标为0的位置的数据
}
//尾删
void SeqListPopBack(SeqList* ps)
{
    SeqListErase(ps, ps->size - 1);//删除下标为ps->size - 1的位置的数据
}

11.查找数据

查找数据也相对简单,直接遍历一次顺序表即可,若找到了目标数据,则停止遍历,并返回该数据的下标,否则返回-1

//查找元素,若有,返回下标,否则返回-1
int SeqListFind(SeqList* ps, SLDataType x)
{
    assert(ps);
    int i = 0;
    for (i = 0; i < ps->size; i++)//遍历顺序表进行查找
    {
        if (ps->a[i] == x)
            return i;//找到该数据,返回下标
    }
    return -1;//未找到,返回-1
}

12.修改数据

修改数据,就直接对该位置的数据进行再次赋值即可。

//修改指定下标位置元素
void SeqListModify(SeqList* ps, int pos, SLDataType x)
{
    assert(ps);
    assert(pos >= 0 && pos < ps->size);//检查输入下标的合法性
    ps->a[pos] = x;//修改数据
}

⌚四、顺序表实现源码

Sqlist.h 文件:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<string.h>
#include <assert.h>
#include <stdlib.h>
// #define MAX_SIZE 100
typedef int SQDateType;

typedef struct SeqList
{
    SQDateType* a;
    int size; //有效数据个数
    int cacpcity;//容量
}SL;

//增删查改
void SeqListInit(SL* ps);
void SeqListDestory(SL* ps);
int SeqListFind(SL* ps, SQDateType x); 
int SeqListModity(SL* ps, int pos, SQDateType x);

//头插 尾插  头删  尾删  任意位置插入、删除  
void SeqListPushBack(SL* ps, SQDateType x);
void SeqListPushFront(SL* ps, SQDateType x);

void SeqListPopBack(SL* ps);
void SeqListPopFront(SL* ps);

void SeqListInsert(SL* ps, int pos, SQDateType x);
void SeqListErase(SL* ps, int pos);

//打印
void SeqListPrintf(SL* ps);
//扩容
void SeqLIstCheckCacpcity(SL* ps);

Sqlist.cpp文件;

#include "seqList.h"

void SeqLIstCheckCacpcity(SL* ps)
{
    //判断表满就扩容
    if (ps->size == ps->cacpcity)
    {
        int newcacpcity = ps->cacpcity == 0 ? 4 : ps->cacpcity * 2;

        SQDateType* tmp = (SQDateType*)realloc(ps->a, newcacpcity * 2 * sizeof(SQDateType));

        if (tmp == NULL)
        {
            printf("realloc is fail\n");
            exit(-1);
        }

        else
        {
            ps->a = tmp;
            ps->cacpcity = newcacpcity;
        }
    }
}

//初始化
void SeqListInit(SL* ps)
{
    ps->a = NULL;
    ps->size = 0;
    ps->cacpcity = 0;
}

//尾插
void SeqListPushBack(SL* ps, SQDateType x)
{
    SeqLIstCheckCacpcity(ps);

    ps->a[ps->size] = x;
    ps->size++;

}

void SeqListPrintf(SL* ps)
{
    for (int i = 0; i < ps->size; i++)
    {
        printf("%d ", ps->a[i]);
    }
    printf("\n");

}

//头插
void SeqListPushFront(SL* ps, SQDateType x)
{

    SeqLIstCheckCacpcity(ps);

    int end = ps->size - 1;
    int start = 0;
    while (end>=start)
    {
        ps->a[end + 1] = ps->a[end];
        end--;
    }

    ps->a[0] = x; 
    ps->size++;

}

//尾删
void SeqListPopBack(SL* ps)
{
    assert(ps->size > 0);//size不大于0就断言了

    // ps->a[ps->size - 1] = 0; 可有可无
    ps->size--;

}   

//头删
void SeqListPopFront(SL* ps)
{
    assert(ps->size > 0);

    int start = 1;
    while (start < ps->size)
    {
        ps->a[start-1] = ps->a[start];
        start++;
    }
    ps->size--;

}

//
void SeqListInsert(SL* ps, int pos, SQDateType x)
{
    assert(pos< ps->size );
    SeqLIstCheckCacpcity(ps);

    int end = ps->a[ps->size - 1];
    while (end <= pos)
    {
        ps->a[end + 1] = ps->a[end];
        end--;

    }

    ps->a[pos] = x;
    ps->size++;
}

void SeqListErase(SL* ps, int pos)
{
    assert(pos < ps->size);
    int start = pos+1;
    while (start <ps->size )
    {
        ps->a[start-1] = ps->a[start];
        start++;
    }

    ps->size--;
}

int SeqListFind(SL* ps, SQDateType x)
{
    for (int i = 0; i < ps->size; i++)
    {
        if (ps->a[i] == x)
        {
            return i;
        }
    }

    return -1;

}

int SeqListModity(SL* ps, int pos, SQDateType x)
{
    assert(pos < ps->size);

    ps->a[pos] = x;

    return 0;
}

Text.cpp文件(菜单+测试)

 #define _CRT_SECURE_NO_WARNINGS 1
#include "seqList.h"

void TestSeqList1()
{
    SL sl;
    SeqListInit(&sl);
    SeqListPushBack(&sl, 1);
    SeqListPushBack(&sl, 2);
    SeqListPushBack(&sl, 3);
    SeqListPushBack(&sl, 4);
    SeqListPushBack(&sl, 5);
    SeqListPushBack(&sl, 6);

    SeqListPrintf(&sl);

    SeqListPushFront(&sl,1);
    SeqListPushFront(&sl, 1);
    SeqListPushFront(&sl, 1);
    SeqListPushFront(&sl, 1);
    SeqListPushFront(&sl, 1);
    SeqListPushFront(&sl, 1);

    SeqListPrintf(&sl);

    SeqListPopBack(&sl);
    SeqListPopBack(&sl);

    SeqListPrintf(&sl);

    SeqListPopFront(&sl);
    SeqListPopFront(&sl);

    SeqListPrintf(&sl);

    SeqListInsert(&sl, 1, 20);
    SeqListInsert(&sl, 2, 30);

    SeqListPrintf(&sl);

    SeqListErase(&sl, 2);

    SeqListPrintf(&sl);

    SeqListModity(&sl, 2, 55);

    SeqListPrintf(&sl);

    int Findpos=SeqListFind(&sl , 55);

    printf("下标为%d\n", Findpos);

}

void meau()
{
    printf("********************************************\n");
    printf("(1).头插数据         (2).尾插数据\n");
    printf("(3).头删数据         (4).尾删数据\n");
    printf("(5).打印数据         (6).查找数据\n");
    printf("(7).任意位置插入数据  (8).任意位置删除数据\n");
    printf("(9).任意位置修改数据  (-1).退出程序\n");
    printf("********************************************\n");

    printf("\n**请输入你的操作选项:>**\n");

}

int main()
{
    int option = 0;
    int x = 0;
    int y = 0;
    SL s;
    SeqListInit(&s);

    while (option !=-1)
    {
        meau();
        scanf("%d",&option);

        switch (option)
        {
        case 1:
            printf("请输入你要头插的数据,用-1结束\n");
            do
            {
                scanf("%d", &x);
                if (x != -1)
                {
                    SeqListPushFront(&s, x);
                }

            } while (x!=-1);
            break;

        case 2:
            printf("请输入你要尾插的数据,以-1结束\n");
            do
            {
                scanf("%d", &x);
                if (x != -1)
                {
                    SeqListPushBack(&s, x);
                }

            } while (x != -1);
            break;

        case 3:
            printf("头删数据,输入1确定,输入-1退出\n");
            do
            {
                scanf("%d", &x);
                if (x == 1)
                {
                    SeqListPopFront(&s);
                }

            } while (x == 1);
            break;

        case 4:
            printf("尾删数据,输入1确定,输入-1退出\n");
            do
            {
                scanf("%d", &x);
                if (x == 1)
                {
                    SeqListPopBack(&s);
                }

            } while (x == 1);
            break;

        case 5:
            SeqListPrintf(&s);
            break;

        case 6:
            printf("请输入你要查找的数据(返回输出下标,没找到输出-1),输入-1结束\n");
            do
            {
                scanf("%d", &x);
                int a = x;
                if (x != -1)
                {
                    int Findpos = SeqListFind(&s, a);

                    if (Findpos == -1)
                    {
                        printf("没有找到\n");
                    }
                    else
                    {
                        printf("下标为%d", Findpos);
                    }

                }
            } while (x != -1);
            break;

        case 7:
            printf("请输入你 插入数据位置的下标 和 插入数据,输入-1结束\n");
            do
            {
                scanf("%d", &x);
                if (x != -1)
                {
                    scanf("%d", &y);
                    SeqListInsert(&s,x,y);
                }

            } while (x != -1);
            break;

        case 8:
            printf("请输入你要删除位置的下标,输入-1结束\n");
            do
            {
                scanf("%d", &x);
                if (x != -1)
                {
                    SeqListPushBack(&s, x);
                }

            } while (x != -1);
            break;

        case 9:
            printf("请输入你 修改数据位置的下标 和 修改的数据,输入-1结束\n");
            do
            {
                scanf("%d", &x);
                if (x != -1)
                {
                    scanf("%d", &y);
                    SeqListModity(&s, x, y);
                }

            } while (x != -1);
            break;

        case -1:
            printf("退出\n");
            return 0;
            break;

        default:
            printf("输入错误,请重新输入\n");
            break;
        }

    }

    //测试用例
    //TestSeqList1();

    return 0;
}

【数据结构】——拿捏顺序表 ( 详细 )