数据结构实验一:顺序表的基本操作

选用教材为“数据结构(C语言版 第三版)李冬梅 严蔚敏 吴伟民编著”

实验工具使用手册

工具:CLion【A cross-platform IDE for C and C++】--对非商业用途免费(薅它)

安装:详细步骤略,注意在初始化选项中,勾选所有选项。

新建项目:以新建c语言项目为例-->

插件:安装通义灵码插件,使用情况如下,帮助回答代码问题,辅助编程。

实验一线性表的顺序表示和实现:

  1. 顺序表初始化;
  2. 在顺序表上插入元素;
  3. 删除顺序表上的元素;
  4. 在顺序表上定位;
  5. 输出顺序表;
  6. 销毁顺序表。

线性表的顺序结构定义

#define  LIST_INIT_SIZE  100 // 线性表存储空间的初始分配量

#define  LISTINCREMENT 10 // 线性表存储空间的分配增量

typedef  struct {

       ElemType  *elem; // 存储空间基址

       int        length; // 当前长度

       int   listsize;  // 当前分配的存储容量  

                         // (以sizeof(ElemType)为单位) 

       }SqList; // 俗称 顺序表

相关算法

Status InitList_Sq( SqList& L ) {

  // 构造一个空的线性表

L.elem = (ElemType*) malloc (LIST_

                  INIT_SIZE*sizeof (ElemType)); 

if (!L.elem) exit(OVERFLOW);

L.length = 0;

L.listsize = LIST_INIT_SIZE

return OK;

} // InitList_Sq      


int LocateElem_Sq(SqList L, ElemType e,  Status (*compare)(ElemType, ElemType)) { 

   // 在顺序表中查询第一个满足判定条件的数据元素,

  // 若存在,则返回它的位序,否则返回 0

i = 1;           // i 的初值为第 1 元素的位序

p = L.elem;      // p 的初值为第 1 元素的存储位置

while (i <= L.length &&

                       !(*compare)(*p++, e))  ++i;

if (i <= L.length)  return i; 

else  return 0;

} // LocateElem_Sq


status ListInsert_Sq(sqlist &L,int i,ElemType e){

       If  (i<1||i>L.length+1)  return  ERROR;

       If  (L.length>=L.listsize){

              Newbase =(elemtype*)realloc(L.elem,

              (L.listsize+listincrement)*sizeof(ElemType));

              if (!newbase) exit (overflow);

              l.elem =newbase;

              l.listsize+=listincrement;

              }

       q=&(L.elem[i-1]); // q 指示插入位置

       for  (p=&(L.elem[L.length-1]);p>=q;--p)   *(p+1)=*p; 

                                                   // 插入位置及之后的元素右移

       *q=e; // 插入e 

       ++L.length; // 表长增1 

       return OK;

       }



status  listdelete_sq(sqlist &L,int i,elemtype &e){

       if ((i<1)||(i>L.length)) return error;

       p=&(L.elem[i-1]); // p 为被删除元素的位置

       e=*p; // 被删除元素的值赋给 e

       q=L.elem+L.length-1; // 表尾元素的位置

       for (++p;p<=q;++p)  *(p-1)=*p; // 被删除元素之后的元素左移

       --L.length; // 表长减1

       return ok;

       }

实验要求

(1) 程序要添加适当的注释,程序的书写要采用缩进格式。

(2) 程序要具有一定的健壮性,即当输入数据非法时,程序也能适当地做出反应,如插入删除时指定的位置不对等等。

(3) 程序要做到界面友好,在程序运行时用户可以根据相应的提示信息进行操作。

程序代码

// 顺序表 (C语言版)
#include <stdio.h>//包含标准输入输出库头文件,提供基本的输入输出函数,如printf()和scanf()
#include <stdlib.h>//提供内存管理、程序控制等常用函数,在代码中用于 malloc()、realloc()、free() 等动态内存操作,也用于 exit() 函数来终止程序
#include <stdbool.h>//提供 bool、true、false 等布尔类型支持,使代码可以使用布尔逻辑进行条件判断

// 初始化存储的元素个数
#define INIT_SIZE 10
// 增长因子:每次增长为原来的两倍
#define ADD_TWICE 2  

// 存储的元素类型,这里使用 int 类型,可根据需要修改为对应的数据类型
typedef int ElemType;

// 顺序表的存储结构
typedef struct {
    ElemType* data;
    int max_size; // 表中最多存储元素个数
    int now_size; // 当前表中存储元素个数
} Sqlist;

// 显示菜单
//\t表示一个制表符,制表符作用是把输出位置移到下一个Tab位,Tab位是8个字符的倍数,Tab位是4个字符的倍数,Tab位是2个字符的倍数,Tab位是1个字符的倍数
// \n表示一个回车符,回车符作用是把输出位置移到下一行开头
void show() {
    printf("\n\n\t顺序表基础操作\n");
    printf("\t1--新建顺序表\n");
    printf("\t2--表尾插入数据\n");
    printf("\t3--遍历显示顺序表\n");
    printf("\t4--当前表长\n");
    printf("\t5--输出特定位置上的元素\n");
    printf("\t6--输出特定元素的位置\n");
    printf("\t7--在特定的位置上插入数据\n");
    printf("\t8--删除特定位置上的元素\n");
    printf("\t9--销毁顺序表\n");
}

bool judge_fulled(Sqlist* sq) {
    return sq->max_size == sq->now_size;
}

bool judge_empty(Sqlist* sq) {
    return sq->now_size == 0;
}

// 初始化顺序表
bool init_sqlist(Sqlist* sq) {
    sq->data = (ElemType*)malloc(sizeof(ElemType) * INIT_SIZE);
    if(sq->data == NULL) {
        printf("开辟顺序表数据存储空间失败\n");
        exit(-1);	
    }
    sq->max_size = INIT_SIZE;
    sq->now_size = 0;
    return true;
}

// 销毁顺序表
bool delete_sqlist(Sqlist* sq) {
    if(sq == NULL) {
        printf("当前表不存在\n");
        return false;		
    }
    if(judge_empty(sq)) {
        printf("当前表内为空\n");
        return false;
    }
    free(sq->data);
    sq->data = NULL;
    printf("已销毁\n");
    return true;
}

// 顺序表扩容
bool sq_growth(Sqlist* sq) {
    ElemType* new_data = (ElemType*)realloc(sq->data, sizeof(ElemType) * sq->max_size * ADD_TWICE);
    if(new_data == NULL) {
        printf("扩容失败\n");
        exit(-1);
    }
    sq->data = new_data;
    sq->max_size *= ADD_TWICE;
    return true;
}

// 插入数据函数
bool add_data(Sqlist* sq, ElemType data) {
    if(judge_fulled(sq)) sq_growth(sq);
    sq->data[sq->now_size++] = data;
    return true;
}

// 批量插入数据
bool add_datas(Sqlist* sq) {
    int count, i;
    ElemType temp;
    printf("请输入要插入元素的个数:");
    scanf("%d", &count);
    for(i = 0; i < count; i++) {
        printf("请输入第%d个元素:", i+1);
        scanf("%d", &temp);
        add_data(sq, temp);
    }
    printf("插入完成\n");
    return true;
}

// 显示顺序表
bool sq_show(Sqlist* sq) {
    if(sq == NULL) {
        printf("当前表不存在\n");
        return false;
    }
    if(judge_empty(sq)) {
        printf("当前表内为空\n");
        return false;
    }
    printf("表内元素为:");
    for(int i = 0; i < sq->now_size; i++)
        printf("%d ", sq->data[i]);
    printf("\n");
    return true;
}

// 获取表长
int sq_length(Sqlist* sq) {
    return sq ? sq->now_size : 0;
}

// 输出指定位置的元素
bool get_data(Sqlist* sq, int addr) {
    addr -= 1;
    if(addr < 0 || addr >= sq->now_size) {
        printf("访问位置不合法\n");
        return false;
    }
    printf("此位置上的元素为:%d\n", sq->data[addr]);
    return true;
}

// 输出指定元素位置
bool get_addr(Sqlist* sq, ElemType data) {
    for(int i = 0; i < sq->now_size; i++) {
        if(sq->data[i] == data) {
            printf("此元素在表中处于第%d个位置\n", i+1);
            return true;
        }
    }
    printf("此元素不存在于表中\n");
    return false;
}

// 在指定位置插入数据
bool insert_data(Sqlist* sq, ElemType data, int addr) {
    addr -= 1;
    if(addr < 0 || addr > sq->now_size) {
        printf("此位置不合法\n");
        return false;
    }
    if(judge_fulled(sq)) sq_growth(sq);
    for(int i = sq->now_size; i > addr; i--)
        sq->data[i] = sq->data[i-1];
    sq->data[addr] = data;
    sq->now_size++;
    return true;
}

//在指定位置删除数据
bool delete_data(Sqlist* sq, int addr) {
    addr -= 1;
    if(addr < 0 || addr >= sq->now_size) {
        printf("此位置不合法\n");
        return false;
    }
    for(int i = addr; i < sq->now_size-1; i++)
        sq->data[i] = sq->data[i+1];
    sq->now_size--;
    return true;
}

// 菜单功能选择
void switch_channel(int channel, Sqlist* sq) {
    switch(channel) {
        case 1: init_sqlist(sq); break;
        case 2: add_datas(sq); break;
        case 3: sq_show(sq); break;
        case 4: printf("当前表长:%d\n", sq_length(sq)); break;
        case 5: {
            int addr;
            printf("请输入查询的元素位置:");
            scanf("%d", &addr);
            get_data(sq, addr);
        } break;
        case 6: {
            int data;
            printf("请输入查询的元素:");
            scanf("%d", &data);
            get_addr(sq, data);
        } break;
        case 7: {
            int addr, data;
            printf("请输入插入的元素:");
            scanf("%d", &data);
            printf("请输入插入元素的位置:");
            scanf("%d", &addr);
            insert_data(sq, data, addr);
        } break;
        case 8: {
            int addr;
            printf("请输入删除的元素位置:");
            scanf("%d", &addr);
            delete_data(sq, addr);
        } break;
        case 9:
            delete_sqlist(sq);
            break;
    }
}

int main() {
    Sqlist s;
    Sqlist* sq = &s;
    int channel;

    while (1) {
        show();
        scanf("%d", &channel);
        //system("cls"); // Windows 清屏
        switch_channel(channel, sq);
        // system("pause");
    }
    return 0;
}

各部分函数的讲解:

  • 菜单功能选择

        switch_channel 函数是菜单功能选择的核心实现,它通过以下方式工作:

1. switch-case 语句
- 使用 switch 语句根据 channel 值选择执行不同的代码块
- 每个 case 对应一个菜单选项

2.需要用户输入的复杂操作
        对于需要额外参数的操作(如查询、插入、删除),函数内嵌套了用户输入代码:
case 5: {                           // 查询特定位置元素
    int addr;
    printf("请输入查询的元素位置:");
    scanf("%d", &addr);             // 获取用户输入的位置
    get_data(sq, addr);             // 调用查询函数
} break;

3.执行流程:

main函数调用:

show();                          // 显示菜单

scanf("%d", &channel);           // 获取用户选择

switch_channel(channel, sq);     // 执行对应功能
功能匹配:
   - 用户输入数字1-9
   - switch 语句匹配对应的 case
   - 执行相应的函数调用

参数传递:
   - 所有操作都需要 Sqlist* sq 指针作为参数
   - 复杂操作额外获取用户输入参数

这种设计将用户界面与功能实现分离,使程序结构清晰,易于维护和扩展。

  • 初始化顺序表

sq->data = (ElemType*)malloc(sizeof(ElemType) * INIT_SIZE);

这行代码是顺序表初始化的核心语句,用于动态分配内存空间:

1. malloc:
   - C标准库函数,用于动态分配内存
   - 在堆(heap)上分配指定大小的连续内存空间

2. sizeof(ElemType) * INIT_SIZE:
   - 计算需要分配的总字节数
   - sizeof(ElemType):单个元素占用的字节数(这里是int,通常为4字节)
   - INIT_SIZE:初始容量大小(定义为10)
   - 总共分配 4字节 × 10 = 40字节的内存空间

3. (ElemType*):
   - 强制类型转换,将malloc返回的void*转换为ElemType*指针
   - 使指针类型与数据类型匹配

4. sq->data:
   - 将分配的内存地址赋值给顺序表结构体的data指针成员
   - 使data指针指向这块新分配的内存空间

  • 销毁顺序表

delete_sqlist 函数用于彻底删除顺序表并释放其占用的所有动态内存资源,防止内存泄漏。

详细实现过程

1. 安全检查阶段
// 检查顺序表指针是否存在
if(sq == NULL) {
    printf("当前表不存在\n");
    return false;        
}

- 首先验证传入的顺序表指针是否有效
- 防止对空指针进行解引用操作导致程序崩溃


// 检查顺序表是否为空
if(judge_empty(sq)) {
    printf("当前表内为空\n");
    return false;
}

- 调用 judge_empty 函数检查表中是否有数据
- 如果表为空,提示用户并返回(避免销毁一个空表)

2. 资源释放阶段
free(sq->data);    // 释放数据存储空间
sq->data = NULL;   // 避免野指针

- 使用 free()函数释放通过malloc()分配的内存
- 将指针置为 NULL,防止后续误用造成内存访问错误

  • 顺序表扩容

这个 sq_growth 函数用于顺序表的动态扩容,当表满时自动增加存储容量。

1. 核心扩容操作
ElemType* new_data = (ElemType*)realloc(sq->data, sizeof(ElemType) * sq->max_size * ADD_TWICE);
  - 使用 realloc 函数重新分配内存:
  - 第一个参数:原内存指针 sq->data
  - 第二个参数:新的内存大小 = 原大小 × 扩容因子
  - sizeof(ElemType) * sq->max_size * ADD_TWICE 计算新内存大小
  - ADD_TWICE 定义为2,表示容量翻倍

2. 错误处理
if(new_data == NULL) {
    printf("扩容失败\n");
    exit(-1);
}

- 检查 realloc 是否成功分配内存
- 如果分配失败(返回NULL),输出错误信息并终止程序

3. 更新顺序表信息
sq->data = new_data;        // 更新数据指针
sq->max_size *= ADD_TWICE;  // 更新最大容量(翻倍)

- 将新分配的内存地址赋给 sq->data
- 将最大容量更新为原来的2倍

扩容时机:
当 judge_fulled 判断表已满时,add_data 和 insert_data 函数会自动调用此函数进行扩容。

内存管理特点:
1. 保持数据不变:realloc 会尽量在原地址扩展内存,如果不行则分配新内存并复制原数据
2. 容量翻倍:每次扩容都将容量增加一倍,减少频繁扩容的开销
3. 自动处理:对用户透明,无需手动调用

示例场景:
初始状态:容量10个元素,已存10个 → 表满
执行插入:触发扩容
扩容后:容量20个元素,已存10个 → 可继续插入10个元素

-->这个函数是实现动态顺序表的关键,保证了顺序表可以根据需要自动增长容量。

  • 获取表长
int sq_length(Sqlist* sq) {
    return sq ? sq->now_size : 0;
}

        这行代码是 sq_length 函数的返回语句,使用了条件运算符(三元运算符)来简洁地处理边界情况:

语法结构:
这是C语言中的条件运算符,格式为:条件 ? 值1 : 值2

执行逻辑:

1. 条件判断:sq 
   - 检查 sq 指针是否为非空(真值)
   
2. 分支处理:
   - 如果 sq 不为空(true):返回 sq->now_size(当前元素个数)
   - 如果 sq 为空(false):返回 0

等价写法:
这段代码等价于以下if-else写法:
if(sq != NULL) {
    return sq->now_size;
} else {
    return 0;
}

设计意义:

1. 安全性考虑
- 防止对空指针解引用导致程序崩溃
- 当顺序表未初始化或已被销毁时,sq 可能为 NULL

2. 逻辑合理性
- 如果顺序表不存在,长度自然为0
- 避免返回未定义的值

3. 代码简洁性
- 用一行代码完成安全检查和值返回
- 比传统的if-else写法更简洁

使用场景:
当用户选择菜单选项4"当前表长"时,会调用此函数:
case 4: printf("当前表长:%d\n", sq_length(sq)); break;

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值