数据结构02--顺序表

aaaaa

1. 线性表

1.1 概念

1.2 举例

        生活中的线性表例子非常多,比如一个班级中的以学号编排的学生,一座图书馆中的以序号编排的图书、一条正常排队等候的队列、一摞从上到下堆叠的餐盘,这些都是线性表。他们的特点都是:除了首尾两个元素,其余任何一个元素前后都对应相邻的另一个元素。

注意:线性表是一种数据内部的逻辑关系,与存储形式无关线性表既可以采用连续的顺序存储,也可以采用离散的链式存储

2.顺序表

2.1 基本概念

  • 顺序表:顺序存储线性表
  • 链式表:链式存储的线性表,简称链表。

        顺序存储就是将数据存储到一片连续的内存中,在C语言环境下,可以是具名的栈数组,或者是匿名的堆数组

        存储方式不仅仅只是提供数据的存储空间,而是必须要能体现数据之间的逻辑关系。当采用顺序存储的方式来存放数据时,唯一能用来表达数据间本身的逻辑关系的就是存储位置。比如队列中的两个人,小明和小花,如果小明在逻辑上排在相邻的小花的前面,那么在存储位置上也必须把小明存放在相邻的小花的前面。

2.2 基本操作

  • 顺序表设计(管理结构体)
    1. 顺序表总容量
    2. 顺序表当前最末元素下标位置
    3. 顺序表指针

管理顺序表结构体:

// 管理顺序表的结构体
typedef struct
{
    int capcity;  // 顺序表容量
    int last;     // 顺序表最末元素下标
    data_t *data; // 顺序表
} sequencelist;

顺序表数据类型:

// 顺序表数据类型
typedef struct data
{
    int num;
    char name[30];
} data_t;

初始化顺序表:

/// @brief 初始化管理顺序表的结构体
/// @param len 顺序表的容量
/// @return 初始化完成的结构体地址
sequencelist *initCntl(int len)
{
    // 在堆内存中申请一个管理顺序表结构体的空间
    sequencelist *cntl = (sequencelist *)calloc(1, sizeof(sequencelist));
    if (cntl == NULL)
    {
        printf("申请管理顺序表结构体空间失败.\n");
        perror("CALLOC ERROR");
        return NULL;
    }

    // 初始化管理结构体各成员
    // 顺序表入口地址以及申请存放顺序表数据空间
    cntl->data = (data_t *)calloc(len, sizeof(data_t));
    if (cntl->data == NULL)
    {
        printf("申请顺序表数据空间失败.\n");
        perror("CALLOC ERROR");
        return NULL;
    }

    // 顺序表总容量
    cntl->capcity = len;
    // 顺序表最末元素下标
    cntl->last = -1;

    return cntl;
}

将数据插入到顺序表中:

/// @brief 将数据头插到顺序表中
/// @param cntl 顺序表
/// @param newData 新数据的地址
/// @return 插入成功返回真,失败返回假
bool addCntlHead(sequencelist *cntl, data_t *newData)
{
    // 判断顺序表是否已满,注意顺序表下标从零开始,容量从1开始
    if (cntl->last == cntl->capcity - 1)
    {
        printf("顺序表已满\n");
        return false;
    }
    // 添加数据前更新顺序表下标
    cntl->last++;
    // 将数据拷贝到顺序表中
    //  *(cntl->data+cntl->last) = *newData;
    //  cntl->data[cntl->last] = *newData;
    memcpy(cntl->data + cntl->last, newData, sizeof(data_t));

    return true;
}

/// @brief 将数据按排序顺序插入到顺序表中
/// @param cntl 顺序表
/// @param newData 新数据的地址
/// @return 插入成功返回真,失败返回假
bool addCntlOrder(sequencelist *cntl, data_t *newData)
{
    // 判断顺序表是否已满,注意顺序表下标从零开始,容量从1开始
    if (cntl->last == cntl->capcity - 1)
    {
        printf("顺序表已满.\n");
        return false;
    }

    // 寻找合适位置插入
    int tmp;
    for (tmp = 0; (newData->num > cntl->data[tmp].num) &&
                  tmp <= cntl->last;
         tmp++)
        ;
    // 将tmp之后的数据右移,留出tmp的位置插入
    for (int i = cntl->last; i >= tmp; i--)
    {
        memcpy(cntl->data + i + 1, cntl->data + i, sizeof(data_t));
    }
    // 拷贝新数据到顺序表中
    memcpy(cntl->data + tmp, newData, sizeof(data_t));

    // 更新顺序表下标
    cntl->last++;

    return true;
}

判断顺序表是否为空:

/// @brief 判断顺序表是否为空
/// @param cntl 顺序表
/// @return 为空返回真,不为空返回假
bool isCntlEmpty(sequencelist *cntl)
{
    return (cntl->last == -1);
}

修改顺序表的数据:

/// @brief 修改顺序表的数据
/// @param cntl 顺序表
/// @return 成功修改返回true,失败返回false
bool reviseCntl(sequencelist* cntl)
{
    int reviseData,reviseResult;
    printf("请输入要修改的数据.\n");
    scanf("%d",&reviseData);
    reviseResult = findCntl(cntl,reviseData);
    if (reviseResult==-1)
    {
        printf("未查找到要修改的数据.\n");
        return false;
    }
    
    printf("请输入编号名字修改:\n");
    //第一个的类型为int类型,所以需要取地址&,第二个的类型为char *类型,所以不用取地址符
    scanf("%d%s",&cntl->data[reviseResult].num,cntl->data[reviseResult].name);
    printf("修改成功.\n");
    return true;
}

快速排序排序顺序表的元素:

/// @brief 快排划分支点左右两边元素
/// @param cntl 顺序表
/// @param low 参与排序元素的最小下标
/// @param high 参与排序元素的最大下标
/// @return 支点的坐标
int pivot(sequencelist *cntl, int low, int high)
{
    //临时变量tmp存放选取支点的值
    int tmp = cntl->data[low].num;
    //high=low一次支点左右划分结束
    while (high > low)
    {
        //循环遍历比支点小的high的元素
        while (high > low && cntl->data[high].num >= tmp)
        {
            high--;
        }
        //跳出循环时high定位到比支点小的元素,交换元素把比支点小的元素放到low的位置
        swapCntl(&cntl->data[low], &cntl->data[high]);
        //循环遍历比支点大的low的元素
        while (high > low && cntl->data[low].num <= tmp)
        {
            low++;
        }
        //跳出循环时low定位到比支点大的元素,交换元素把比支点大的元素放到high的位置
        swapCntl(&cntl->data[low], &cntl->data[high]);
    }
    // 循环结束,此时low等于high也是tmp的位置,把支点放进去
    memcpy(&cntl->data[low], &tmp, sizeof(tmp));

    return high;
}

/// @brief 快速排序
/// @param cntl 顺序表
/// @param low 参与排序元素最小下标
/// @param high 参与排序元素最大下标
void quickSort(sequencelist *cntl, int low, int high)
{
    //high=low递归结束条件,快速排序结束
    if (high > low)
    {
        //调用一次支点划分并返回支点
        int piv = pivot(cntl, low, high);
        //对支点左边继续划分
        quickSort(cntl, low, piv - 1);
        //对支点右边继续划分
        quickSort(cntl, piv + 1, high);

        return;
    }
    return;
}

遍历打印顺序表的元素:

/// @brief 打印顺序表
/// @param cntl 顺序表
void display(sequencelist *cntl)
{
    if (isCntlEmpty(cntl))
    {
        printf("顺序表为空.\n");
        return;
    }
    for (int i = 0; i <= cntl->last; i++)
    {
        printf("[%d]:%s\n", cntl->data[i].num, cntl->data[i].name);
    }

    return;
}

查找顺序表中的元素:

/// @brief 查找数据并返回查找到的数据的下标
/// @param cntl 顺序表
/// @param findData 要查找的数据
/// @return 查找成功返回查找到的数据的下标,失败返回-1
int findCntl(sequencelist *cntl, int findData)
{
    if (isCntlEmpty(cntl))
    {
        printf("顺序表为空.\n");
        return -1;
    }
    for (int i = 0; i <= cntl->last; i++)
    {
        if (cntl->data[i].num == findData)
        {
            return i;
        }
    }

    return -1;
}


/// @brief 二分法查找
/// @param cntl 顺序表
/// @param findData 待查找的数据
/// @param left 
/// @param right 
/// @return 数据查找成功返回其下标,失败返回-1
int twoFindCntl(sequencelist *cntl,int findData)
{
    if (isCntlEmpty(cntl))
    {
        printf("顺序表为空.\n");
        return -1;
    }
    quickSort(cntl,0,cntl->last);
    printf("此为二分法查找,顺序表已排序好.\n");
    int left = 0,right = cntl->last ;
    while (right >= left)
    {
        int mid = left+(right-left)/2;
        if (cntl->data[mid].num==findData)
        {
            return mid;
        }
        else if (cntl->data[mid].num > findData)
        {
            right = mid-1;
        }
        else if (cntl->data[mid].num < findData)
        {
            left = mid+1;
        }
        
    }
    return -1;
}

删除顺序表的元素:

/// @brief 删除顺序表中的数据
/// @param cntl 顺序表
/// @param delData 待删除的数据
/// @return 删除成功返回真,删除失败返回假
bool delCntl(sequencelist *cntl, int delData)
{
    int del;
    // 查找待删除的数据,并返回其下标
    del = findCntl(cntl, delData);
    if (del == -1)
    {
        printf("未查找到数据.\n");
        return false;
    }
    for (int i = del; i < cntl->last; i++)
    {
        // 将数据左移并覆盖待删除的数据
        memcpy(cntl->data + i, cntl->data + i + 1, sizeof(data_t));
    }
    // 更新顺序表下标
    cntl->last--;

    return true;
}

销毁顺序表:

// 销毁顺序表

    //先销毁顺序表的数据域
    if (cntl->data != NULL)
    {
        free(cntl->data);
        cntl->data = NULL;  //避免野指针
    }
    
    free(cntl);
    cntl = NULL;    //避免野指针

判断堆内存空间是否全部释放:

ubuntu下运行代码加上运行命令valgrind ./a.out ,如命令行提示未知命令,则需要先安装valgrind

顺序表示例代码:

/**顺序表
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>

// 顺序表数据类型
typedef struct data
{
    int num;
    char name[30];
} data_t;

// 管理顺序表的结构体
typedef struct
{
    int capcity;  // 顺序表容量
    int last;     // 顺序表最末元素下标
    data_t *data; // 顺序表
} sequencelist;

/// @brief 初始化管理顺序表的结构体
/// @param len 顺序表的容量
/// @return 初始化完成的结构体地址
sequencelist *initCntl(int len)
{
    // 在堆内存中申请一个管理顺序表结构体的空间
    sequencelist *cntl = (sequencelist *)calloc(1, sizeof(sequencelist));
    if (cntl == NULL)
    {
        printf("申请管理顺序表结构体空间失败.\n");
        perror("CALLOC ERROR");
        return NULL;
    }

    // 初始化管理结构体各成员
    // 顺序表入口地址以及申请存放顺序表数据空间
    cntl->data = (data_t *)calloc(len, sizeof(data_t));
    if (cntl->data == NULL)
    {
        printf("申请顺序表数据空间失败.\n");
        perror("CALLOC ERROR");
        return NULL;
    }

    // 顺序表总容量
    cntl->capcity = len;
    // 顺序表最末元素下标
    cntl->last = -1;

    return cntl;
}

/// @brief 判断顺序表是否为空
/// @param cntl 顺序表
/// @return 为空返回真,不为空返回假
bool isCntlEmpty(sequencelist *cntl)
{
    return (cntl->last == -1);
}

/// @brief 获得新数据
/// @return data_t类型的新数据
data_t getData()
{
    data_t newData = {0};
    printf("请输入编号名字.\n");
    while(scanf("%d%s",&newData.num,newData.name)!=2)
    {
        printf("输入错误,请重新输入.\n");
        while(getchar()!='\n');
        continue;
    }
    return newData;
}

/// @brief 将数据头插到顺序表中
/// @param cntl 顺序表
/// @param newData 新数据的地址
/// @return 插入成功返回真,失败返回假
bool addCntlHead(sequencelist *cntl, data_t *newData)
{
    // 判断顺序表是否已满,注意顺序表下标从零开始,容量从1开始
    if (cntl->last == cntl->capcity - 1)
    {
        printf("顺序表已满\n");
        return false;
    }
    // 添加数据前更新顺序表下标
    cntl->last++;
    // 将数据拷贝到顺序表中
    //  *(cntl->data+cntl->last) = *newData;
    //  cntl->data[cntl->last] = *newData;
    memcpy(cntl->data + cntl->last, newData, sizeof(data_t));

    return true;
}

/// @brief 将数据按排序顺序插入到顺序表中
/// @param cntl 顺序表
/// @param newData 新数据的地址
/// @return 插入成功返回真,失败返回假
bool addCntlOrder(sequencelist *cntl, data_t *newData)
{
    // 判断顺序表是否已满,注意顺序表下标从零开始,容量从1开始
    if (cntl->last == cntl->capcity - 1)
    {
        printf("顺序表已满.\n");
        return false;
    }

    // 寻找合适位置插入
    int tmp;
    for (tmp = 0; (newData->num > cntl->data[tmp].num) &&
                  tmp <= cntl->last;
         tmp++)
        ;
    // 将tmp之后的数据右移,留出tmp的位置插入
    for (int i = cntl->last; i >= tmp; i--)
    {
        memcpy(cntl->data + i + 1, cntl->data + i, sizeof(data_t));
    }
    // 拷贝新数据到顺序表中
    memcpy(cntl->data + tmp, newData, sizeof(data_t));

    // 更新顺序表下标
    cntl->last++;

    return true;
}

/// @brief 打印顺序表
/// @param cntl 顺序表
void display(sequencelist *cntl)
{
    if (isCntlEmpty(cntl))
    {
        printf("顺序表为空.\n");
        return;
    }
    for (int i = 0; i <= cntl->last; i++)
    {
        printf("[%d]:%s\n", cntl->data[i].num, cntl->data[i].name);
    }

    return;
}

/// @brief 交换顺序表的数据
/// @param a 待交换的数据
/// @param b 待交换的数据
void swapCntl(data_t *a, data_t *b)
{
    data_t tmp;
    memcpy(&tmp, a, sizeof(*a));
    memcpy(a, b, sizeof(*b));
    memcpy(b, &tmp, sizeof(tmp));

    return;
}

/// @brief 快排划分支点左右两边元素
/// @param cntl 顺序表
/// @param low 参与排序元素的最小下标
/// @param high 参与排序元素的最大下标
/// @return 支点的坐标
int pivot(sequencelist *cntl, int low, int high)
{
    //临时变量tmp存放选取支点的值
    int tmp = cntl->data[low].num;
    //high=low一次支点左右划分结束
    while (high > low)
    {
        //循环遍历比支点小的high的元素
        while (high > low && cntl->data[high].num >= tmp)
        {
            high--;
        }
        //跳出循环时high定位到比支点小的元素,交换元素把比支点小的元素放到low的位置
        swapCntl(&cntl->data[low], &cntl->data[high]);
        //循环遍历比支点大的low的元素
        while (high > low && cntl->data[low].num <= tmp)
        {
            low++;
        }
        //跳出循环时low定位到比支点大的元素,交换元素把比支点大的元素放到high的位置
        swapCntl(&cntl->data[low], &cntl->data[high]);
    }
    // 循环结束,此时low等于high也是tmp的位置,把支点放进去
    memcpy(&cntl->data[low], &tmp, sizeof(tmp));

    return high;
}

/// @brief 快速排序
/// @param cntl 顺序表
/// @param low 参与排序元素最小下标
/// @param high 参与排序元素最大下标
void quickSort(sequencelist *cntl, int low, int high)
{
    //high=low递归结束条件,快速排序结束
    if (high > low)
    {
        //调用一次支点划分并返回支点
        int piv = pivot(cntl, low, high);
        //对支点左边继续划分
        quickSort(cntl, low, piv - 1);
        //对支点右边继续划分
        quickSort(cntl, piv + 1, high);

        return;
    }
    return;
}

/// @brief 查找数据并返回查找到的数据的下标
/// @param cntl 顺序表
/// @param findData 要查找的数据
/// @return 查找成功返回查找到的数据的下标,失败返回-1
int findCntl(sequencelist *cntl, int findData)
{
    if (isCntlEmpty(cntl))
    {
        printf("顺序表为空.\n");
        return -1;
    }
    for (int i = 0; i <= cntl->last; i++)
    {
        if (cntl->data[i].num == findData)
        {
            return i;
        }
    }

    return -1;
}

/// @brief 二分法查找
/// @param cntl 顺序表
/// @param findData 待查找的数据
/// @param left 
/// @param right 
/// @return 数据查找成功返回其下标,失败返回-1
int twoFindCntl(sequencelist *cntl,int findData)
{
    if (isCntlEmpty(cntl))
    {
        printf("顺序表为空.\n");
        return -1;
    }
    //二分法查找数据的前提是数据是有序的
    quickSort(cntl,0,cntl->last);
    printf("此为二分法查找,顺序表已排序好.\n");
    int left = 0,right = cntl->last ;
    //right和left不断靠近,最后循环遍历完整个顺序表
    while (right >= left)
    {
        //取中间下标
        int mid = left+(right-left)/2;
        //判断中间元素值是否等于要查找的值
        if (cntl->data[mid].num==findData)
        {
            return mid;
        }
        //如果要查找的值比中间值小,right=mid-1往中间值左边寻找
        else if (cntl->data[mid].num > findData)
        {
            right = mid-1;
        }
        //如果要查找的值比中间值大,left=mid+1往中间值右边寻找
        else if (cntl->data[mid].num < findData)
        {
            left = mid+1;
        }
        
    }
    //没有找到值返回-1
    return -1;
}

/// @brief 修改顺序表的数据
/// @param cntl 顺序表
/// @return 成功修改返回true,失败返回false
bool reviseCntl(sequencelist* cntl)
{
    int reviseData,reviseResult;
    printf("请输入要修改的数据.\n");
    scanf("%d",&reviseData);
    reviseResult = findCntl(cntl,reviseData);
    if (reviseResult==-1)
    {
        printf("未查找到要修改的数据.\n");
        return false;
    }
    
    printf("请输入编号名字修改:\n");
    //第一个的类型为int类型,所以需要取地址&,第二个的类型为char *类型,所以不用取地址符
    scanf("%d%s",&cntl->data[reviseResult].num,cntl->data[reviseResult].name);
    printf("修改成功.\n");
    return true;
}

/// @brief 删除顺序表中的数据
/// @param cntl 顺序表
/// @param delData 待删除的数据
/// @return 删除成功返回真,删除失败返回假
bool delCntl(sequencelist *cntl, int delData)
{
    int del;
    // 查找待删除的数据,并返回其下标
    del = findCntl(cntl, delData);
    if (del == -1)
    {
        printf("未查找到数据.\n");
        return false;
    }
    for (int i = del; i < cntl->last; i++)
    {
        // 将数据左移并覆盖待删除的数据
        memcpy(cntl->data + i, cntl->data + i + 1, sizeof(data_t));
    }
    // 更新顺序表下标
    cntl->last--;

    return true;
}



int main(int argc, char const *argv[])
{
    int len, n, findData, findResult, delData, overFlag = 0, optSort;
    char opt;
    bool result;
    printf("请输入顺序表的容量.\n");
    scanf("%d", &len);
    // 顺序表初始化
    sequencelist *cntl = initCntl(len);
    if (cntl != NULL)
    {
        printf("顺序表初始化成功.\n");
    }
    while (getchar() != '\n');
    while (1)
    {
        printf("请输入选择:\n");
        printf("i.添加数据\ts.排序顺序表\td.显示顺序表\tf.查找数据\tg.修改数据\tx.删除数据\tt.退出\n");
        scanf("%c", &opt);
        switch (opt)
        {
        case 'i':
            printf("请输入要输入多少个数据.\n");
            scanf("%d", &n);
            printf("输入1.将数据头插顺序表中\t2.将数据有序插入到顺序表中\n");
            scanf("%d", &optSort);
            for (int i = 0; i < n; i++)
            {
                // 获得新数据
                data_t newData = getData();
                if (optSort == 1)
                {
                    // 将数据头插添加到顺序表中
                    result = addCntlHead(cntl, &newData);
                }
                else if (optSort == 2)
                {
                    // 将数据有序插入到顺序表中
                    result = addCntlOrder(cntl, &newData);
                }

                if (!result)
                {
                    printf("数据添加失败.\n");
                    break;
                }
            }
            break;

        case 's':
            if (isCntlEmpty(cntl))
            {
                printf("顺序表为空.\n");
                break;
            }
            //快速排序排序顺序表
            quickSort(cntl, 0, cntl->last);

            break;

        case 'd':
            display(cntl);
            break;

        case 'f':
            printf("请输入要查找的数据.\n");
            scanf("%d", &findData);
            //二分法查找
            findResult = twoFindCntl(cntl,findData);
            // findResult = findCntl(cntl, findData);
            if (findResult == -1)
            {
                printf("未查找到数据.\n");
            }
            else
            {
                printf("查找成功,查找数据在顺序表中的下标位置为:[%d]->[%d]:%s\n",
                       findResult, cntl->data[findResult].num, cntl->data[findResult].name);
            }
            break;

            case 'g':
                result =  reviseCntl(cntl);
                if (result)
                {
                    printf("修改成功.\n");
                }else
                {
                    printf("修改失败.\n");
                }
                
                break;
        case 'x':
            printf("请输入要删除的数据\n");
            scanf("%d", &delData);

            if (delCntl(cntl, delData))
            {
                printf("删除成功.\n");
            }
            else
            {
                printf("删除失败.\n");
            }

            break;

        case 't':
            overFlag = 1;
            break;

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

            break;
        }
        //判断是否退出循环
        if (overFlag)
        {
            break;
        }

        while (getchar() != '\n'); // 清空缓冲区,防止影响下一次的%c输入
    }
    // 销毁顺序表
    free(cntl);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值