数据结构——顺序表(c语言笔记)

1.线性结构的特点

在数据元素的非空有限集中,(1)存在惟一的一个被称做"第一个"的数据元素;(2)存在惟一的一个被称做"最后一个"的数据元素;(3)除第一个之外,集合中的每个数据元素均只有一个前驱;(4)除最后一个之外,集合中每个数据元素均只有一个后继。

2.顺序表定义

抽象数据类型(ADT)线性表定义:

ADT List {

数据对象:D={ aᵢ | aᵢ ∈ ElemSet,i=1,2,...,n,n≥0 }

数据关系:R1={ <aᵢ₋₁,aᵢ> |aᵢ₋₁,aᵢ ∈ D,i=2,...,n }

基本操作:插入、删除等(以下会详细说明)}ADT List

特点 说明 优点 缺点 适用场景
存储方式 使用 一组连续的存储单元(数组) 按顺序存放元素 结构简单,容易实现 容量固定(静态),动态扩容需要复制数据,代价较大 数据量较小或变化不大
存取方式 支持 随机存取,可通过下标 O(1) 时间访问任意元素 查找第 i 个元素效率高 无法快速找到"值"所在位置(要顺序遍历 O(n)) 频繁需要按下标访问元素
存储密度 每个元素都紧密存放,没有额外存储开销 空间利用率高 插入、删除需要移动大量元素,效率低 元素变动不频繁的场景
容量限制 静态:编译时确定大小;动态:需手动扩容(如 realloc) 静态:内存管理简单 静态:可能浪费或不够用;动态:扩容代价高 元素数目可预测,或允许偶尔扩容
内存分布 连续存储,局部性好,CPU 缓存命中率高 访问速度快 必须有足够大的连续内存空间 内存较充足时

3.顺序表代码

main.cpp

cpp 复制代码
int main()
{
    SeqList mylist;               // 定义顺序表变量(具体结构体在其它文件中定义,通常包含数组指针、容量、当前长度等)
    InitSeqList(&mylist);        // 初始化顺序表,分配内存或设置初始长度为0、容量等

    ELemType Item;               // 用于存放用户输入的数据元素。
    int pos;                     // 用于存放位置(索引)输入
    int select = 1;              // 菜单选项;初始化为1,使得第一次进入 while 循环。选择 0 则退出

    while (select)               // 主循环:当 select 为 0 时退出(因为 while(0) 为假)
    {
        /* 打印菜单(多行输出) */
        printf("**************************************\n");
        printf("* [1]  push_back    [2]  push_front  *\n"); 
        printf("* [3]  show_list    [4]  pop_back    *\n");
        printf("* [5]  pop_front    [6]  insert_pos  *\n");
        printf("* [7]  find         [8]  length      *\n"); 
        printf("* [9]  delete_pos   [10]  delete_val *\n");
        printf("* [11] sort         [12]  resver     *\n"); 
        printf("* [13] clear        [14*]  destroy   *\n"); /
        printf("* [0]  quit_system                   *\n");
        printf("**************************************\n");
        printf("请选择:>");
        scanf_s("%d", &select);   // 从 stdin 读取用户选择(注意:scanf_s 在 Visual C 环境下可用;最好检查返回值)
        if (select == 0)          // 若用户输入 0,退出循环(跳出主菜单)
            break;

        switch (select)          // 根据用户输入的选项进入不同分支
        {
        case 1:
            /* push_back:在表尾插入若干元素,直到输入 -1 结束 */
            printf("请输入要插入的数据(-1结束):>");
            /* 这里使用了逗号运算符:
               while (scanf_s("%d", &Item), Item != -1)
               等价于:
                 scanf_s("%d", &Item); // 调用并丢弃返回值
                 while (Item != -1) { ... }
                */
            while (scanf_s("%d", &Item), Item != -1)
            {
                push_back(&mylist, Item); // 将 Item 插入到顺序表末尾
            }
            break;

        case 2:
            /* push_front:在表头插入若干元素,直到输入 -1 结束 */
            printf("请输入要插入的数据(-1结束):>");
            while (scanf_s("%d", &Item), Item != -1)
            {
                push_front(&mylist, Item); // 将 Item 插入到顺序表开头
            }
            break;

        case 3:
            /* show_list:显示顺序表中的所有元素 */
            show_list(&mylist);
            break;

        case 4:
            /* pop_back:删除表尾元素 */
            pop_back(&mylist);
            break;

        case 5:
            /* pop_front:删除表头元素 */
            pop_front(&mylist);
            break;

        case 6:
            /* insert_pos:在指定位置插入单个元素 */
            printf("请输入要插入的数据:>");
            scanf_s("%d", &Item);    // 读取要插入的数据
            printf("请输入要插入的位置:>");
            scanf_s("%d", &pos);    
            insert_pos(&mylist, pos, Item); // 插入函数应包含越界校验(例如 pos 范围)
            break;

        case 7:
            /* find:查找元素并返回下标(没找到可能返回 -1) */
            printf("请输入要查找的数据:>");
            scanf_s("%d", &Item);
            pos = find(&mylist, Item); // find 应返回索引或 -1
            if (pos == -1)
            {
                printf("查找的数据%d在顺序表中不存在.\n", Item);
            }
            else
            {
                printf("查找的数据%d在顺序表中的%d下标位置.\n", Item, pos);
            }
            break;

        case 8:
            /* length:返回顺序表长度(元素个数) */
            printf("顺序表长度为:>%d\n", length(&mylist));
            break;

        case 9:
            /* delete_pos:按位置删除元素 */
            printf("请输入要删除数据的位置:>");
            scanf_s("%d", &pos);
            delete_pos(&mylist, pos);
            break;

        case 10:
            /* delete_val:按值删除 */
            printf("请输入要删除的数据:>");
            scanf_s("%d", &Item);
            delete_val(&mylist, Item);
            break;

        case 11:
            /* sort:对顺序表排序 */
            sort(&mylist);
            break;

        case 12:
            /* resver: 将顺序表反转 */
            resver(&mylist);
            break;

        case 13:
            /* clear:清空顺序表(置长度为0,但保留分配的内存) */
            clear(&mylist);
            break;

            // case 14:
            //     // destroy(&mylist);
            //     // break;
            /* 注意:case 14 被注释掉了,不能让用户去摧毁顺序表,因此放在最后,当用户退出时候直接自动摧毁 */

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

    destroy(&mylist); // 程序退出前释放顺序表占用的资源(例如 free 动态数组),以防内存泄漏
}

Seqlist.h

cpp 复制代码
#ifndef __SEQLIST_H__
#define __SEQLIST_H__

#include<stdio.h>      // 用于 printf/scanf 等 I/O(在头文件中包含 stdio 通常没问题,但建议在实现文件里包含)
#include <malloc.h>    // 提供 malloc/realloc/free 声明
#include <cassert>     // C++ 里的断言头

#define SEQLIST_INIT_SIZE 8   /* 初始分配容量(元素个数) */
#define INC_SIZE          3   /* 每次扩容时增加的容量(固定增量策略) */

typedef int ELemType;        /* 元素类型定义 */

typedef struct SeqList
{
	ELemType* base;   /* 指向动态数组的指针(由 InitSeqList 分配) */
	int       capacity;/* 当前数组的总容量(能装多少元素) */
	int       size;    /* 当前已存放的元素数量(有效元素个数) */
}SeqList;


/* 扩容函数
   功能:确保顺序表至少还能容纳更多元素,通常在 push 时调用。
   返回:bool(true 表示扩容成功或已有足够空间;false 表示分配失败)
*/
bool Inc(SeqList* list);

/* 初始化顺序表*/
void InitSeqList(SeqList* list);

/* 在尾部插入元素 x*/
void push_back(SeqList* list, ELemType x);

/* 在头部插入元素 x*/
void push_front(SeqList* list, ELemType x);

/* 遍历并显示顺序表*/
void show_list(SeqList* list);

/* 删除并丢弃尾部元素(pop)*/
void pop_back(SeqList* list);

/* 删除并丢弃头部元素*/
void pop_front(SeqList* list);

/* 在指定位置 pos 插入元素 x*/
void insert_pos(SeqList* list,int pos, ELemType x);

/* 查找值为 key 的元素,返回下标(约定:找不到返回 -1)*/
int find(SeqList* list, ELemType key);

/* 返回当前顺序表的长度(元素个数)*/
int length(SeqList* list);

/* 按位置删除元素(pos 索引约定同 insert_pos)*/
void delete_pos(SeqList* list,int pos);

/* 按值删除(常见有两种实现)*/
void delete_val(SeqList* list, ELemType key);

/* 排序(sort)*/
void sort(SeqList* list);

/* 反转顺序表(resver)*/
void resver(SeqList* list);

/* 清空顺序表(clear)*/
void clear(SeqList* list);

/* 销毁顺序表(destroy)
   - 功能:释放动态分配的内存(free(list->base)),并把 base 置 NULL,capacity 和 size 置 0(或其它哨值)。
   - 销毁后不要再使用该 SeqList 除非再次 Init。
*/
void destroy(SeqList* list);

/* 合并两个顺序表(merge)*/
void merge(SeqList* lt, SeqList* la, SeqList* lb);

#endif // __SEQLIST_H__

SeqList.cpp

cpp 复制代码
bool Inc(SeqList* list)
{
	ELemType* newbase = (ELemType*)realloc(list->base, sizeof(ELemType) * (list->capacity + INC_SIZE));
	if (newbase == NULL)
	{
		printf("增配空间失败,内存不足.\n");
		return false;
	}
	list->base = newbase;
	list->capacity += INC_SIZE;
	return true;
}
目的

为顺序表增加容量(当前实现为 capacity += INC_SIZE)。

参数

list:目标顺序表指针(要求非 NULL)。

前置条件

list != NULL。list->base 可为 NULL(realloc(NULL, size) 等同 malloc)。

后置条件

若返回 true:list->base 已更新到新地址(可能移动),list->capacity 增加;若 false:内存未改变,原 list->base 仍有效。

实现步骤(当前代码)

调用 realloc(list->base, sizeof(ELemType)*(list->capacity+INC_SIZE)),把返回值存在 newbase。

若 newbase == NULL,打印错误并返回 false(此时原内存未被释放)。

否则将 list->base = newbase 并 list->capacity += INC_SIZE,返回 true。

cpp 复制代码
void InitSeqList(SeqList* list)
{
	list->base = (ELemType*)malloc(sizeof(ELemType) * SEQLIST_INIT_SIZE);
	assert(list->base != NULL);
	list->capacity = SEQLIST_INIT_SIZE;
	list->size = 0;
}
目的

初始化顺序表结构:分配初始内存、设置 capacity 和 size。

实现步骤

malloc 分配 SEQLIST_INIT_SIZE 个元素的内存。

assert(list->base != NULL)(当前实现)------断言失败会终止程序(仅在调试有效)。

设置 list->capacity = SEQLIST_INIT_SIZE,list->size = 0。

cpp 复制代码
void push_back(SeqList* list, ELemType x)
{
	if (list->size >= list->capacity && !Inc(list))
	{
		printf("顺序表空间已满,不能尾部插入数据.\n");
		return;
	}
	list->base[list->size] = x;
	list->size++;
}
目的

在顺序表尾部插入元素 x。

实现步骤

检查 list->size >= list->capacity,若是则调用 Inc(list) 扩容;扩容失败时打印错误并返回。

将 list->base[list->size] = x,然后 list->size++。

cpp 复制代码
void push_front(SeqList* list, ELemType x)
{
	if (list->size >= list->capacity && !Inc(list))
	{
		printf("顺序表空间已满,不能尾部插入数据.\n");
		return;
	}
	for (int i = list->size; i > 0; --i)
	{
		list->base[i] = list->base[i - 1];
	}
	list->base[0] = x;
	list->size++;
}
目的

在顺序表头部插入元素(把现有元素整体右移一位)。

实现步骤

若空间不足,调用 Inc(list) 扩容。

从 i = size 到 1 逐项右移:base[i] = base[i-1]。

base[0] = x,size++。

cpp 复制代码
void show_list(SeqList* list)
{
	for (int i = 0; i < list->size; ++i)
	{
		printf("%d", list->base[i]);
	}
	printf("\n");
}
目的

把顺序表元素打印到标准输出,供调试或查看用。

实现步骤

遍历 i=0..size-1,printf("%d", base[i]),最后换行。

cpp 复制代码
void pop_back(SeqList *list)
{
	if (list->size==0)
	{
		printf("顺序表已空,不能尾部删除数据.\n");
		return;
	}
	list->size--;
}
目的

删除尾元素(不返回该元素)。

实现步骤

若 size == 0,打印"顺序表已空"并返回。

否则 size--(可选择清零 base[size])。

cpp 复制代码
void pop_front(SeqList* list)
{
	if (list->size == 0)
	{
		printf("顺序表已空,不能尾部删除数据.\n");
		return;
	}
	for (int i = 0; i < list->size-1; ++i)
	{
		list->base[i] = list->base[i + 1];
	}
	list->size--;
}
目的

删除头元素并将后续元素前移一位。

实现步骤

检查 size 是否为 0。

将 base[1..size-1] 左移到 base[0..size-2],size--。

cpp 复制代码
void insert_pos(SeqList* list, int pos, ELemType x)
{
	if (pos<0 || pos>list->size)
	{
		printf("插入数据的位置非法,不能插入数据.\n");
		return;
	}
	
		for (int i = list->size; i > pos; --i)
		{
			list->base[i] = list->base[i - 1];
		}
		list->base[pos] = x;
		list->size++;
}
目的

在下标 pos(约定为 0..size)处插入元素 x,插入后 x 的索引为 pos。

实现步骤

检查 pos 是否在合法范围 0 <= pos <= size,否则打印错误并返回。

从 i = size 到 pos+1 右移元素(base[i] = base[i-1])。

base[pos] = x,size++。

cpp 复制代码
int find(SeqList* list, ELemType key)
{
	for (int i = 0; i < list->size; ++i)
	{
		if (list->base[i] == key)
			return i;
	}
	return -1;
}
目的

线性查找第一个等于 key 的元素,下标从 0 开始,找不到返回 -1。

实现步骤

遍历 i=0..size-1,比较 base[i] == key,命中返回 i。

cpp 复制代码
int length(SeqList* list)
{
	return list->size;
}
目的

返回当前元素个数 size。

实现步骤

直接 return list->size;

cpp 复制代码
void delete_pos(SeqList* list, int pos)
{
	if (pos<0||pos>=list->size)
	{
		printf("删除数据的位置非法,不能删除数据.\n"); 
		return;
	}
	for (int i = pos; i < list->size - 1; ++i)
	{
		list->base[i] = list->base[i + 1];
	}
	list->size--;
}
目的

删除下标为 pos 的元素并把后面的元素左移填补空位。

实现步骤

检查 pos 是否在 0 <= pos < size,否则打印错误并返回。

从 i = pos 到 size - 2 执行 base[i] = base[i+1]。

size--。

cpp 复制代码
void delete_val(SeqList* list, ELemType key)
{
	int pos = find(list, key);
	if (pos==-1)
	{
		printf("要删除的数据不存在.\n"); 
		return;
	}
	delete_pos(list, pos);
}
目的

删除第一次出现的值为 key 的元素(如果存在)。

实现步骤

调用 find(list, key) 获取下标 pos。

若 pos == -1 打印不存在并返回。

调用 delete_pos(list, pos)。

cpp 复制代码
void sort(SeqList* list)
{
	for (int i = 0; i < list->size - 1; ++i)
	{
		for (int j = 0; j < list->size - i - 1; ++j)
		{
			if (list->base[j] > list->base[j + 1])
			{
				ELemType tmp = list->base[j];
				list->base[j] = list->base[j + 1];
				list->base[j + 1] = tmp;
			}
		}
	}
}
目的

将顺序表元素排序(当前实现为冒泡排序,升序)。

实现步骤(当前代码)

嵌套两层循环(冒泡):交换相邻逆序对直到整体有序。

cpp 复制代码
void resver(SeqList* list)
{
	if (list->size == 0 || list->size == 1)
		return;

	int low = 0;
	int high = list->size - 1;
	ELemType tmp;
	while (low < high)
	{
		tmp = list->base[low];
		list->base[low] = list->base[high];
		list->base[high] = tmp;

		low++;
		high--;
	}
}
目的

就地反转顺序表元素顺序。

实现步骤

若 size <= 1 直接返回。

用 low=0, high=size-1,交换 base[low] 和 base[high],low++,high--,直到 low >= high。

cpp 复制代码
void clear(SeqList* list)
{
	list->size = 0;
}
目的

逻辑上清空顺序表(把 size 置 0),但保留 base 内存以供复用。

实现步骤

list->size = 0;

cpp 复制代码
void destroy(SeqList* list)
{
	free(list->base);
	list->base = NULL;
	list->capacity = 0;
	list->size = 0;
}
目的

销毁顺序表:释放 base 指向的动态内存并将结构复位。

实现步骤

free(list->base)(如果 base == NULL,free 是安全的)。

list->base = NULL; list->capacity = 0; list->size = 0;

4..双指针合并两个顺序表

main.cpp

cpp 复制代码
#include"SeqList.h"

int main()
{
	SeqList mylist, youlist, list;
	InitSeqList(&mylist);
	InitSeqList(&youlist);

	push_back(&mylist, 1);
	push_back(&mylist, 3);
	push_back(&mylist, 5);
	push_back(&mylist, 7);
	push_back(&mylist, 9);

	push_back(&youlist, 2);
	push_back(&youlist, 4);
	//push_back(&youlist,6);
	push_back(&youlist, 8);
	//push_back(&youlist,10);

	merge(&list, &mylist, &youlist);
	show_list(&list);
}
cpp 复制代码
void merge(SeqList* lt, const SeqList* la, const SeqList* lb) {
    if (!lt || !la || !lb) return;
    int total = la->size + lb->size;
    if (lt->base) free(lt->base);         // 先释放已有内存,避免泄露
    lt->base = malloc(total * sizeof(ELemType));
    if (!lt->base) { perror("malloc"); lt->capacity = lt->size = 0; return; }
    lt->capacity = total;
    int ia=0, ib=0, ic=0;
    while (ia < la->size && ib < lb->size) {
        if (la->base[ia] <= lb->base[ib]) lt->base[ic++] = la->base[ia++];
        else lt->base[ic++] = lb->base[ib++];
    }
    while (ia < la->size) lt->base[ic++] = la->base[ia++];
    while (ib < lb->size) lt->base[ic++] = lb->base[ib++];
    lt->size = ic;
}
目的

将两个有序顺序表 la、lb 合并为 lt(有序合并),常用于归并排序的合并步骤或合并两个已排序数组。

实现步骤

设 lt->capacity = la->size + lb->size。

lt->base = malloc(...)(没有先释放原 lt->base)。

使用双指针 ia, ib 遍历 la 和 lb,把较小元素依次写入 lt->base[ic++]。

将剩余元素追加。

lt->size = la->size + lb->size;

相关推荐
java搬砖工-苤-初心不变2 小时前
OpenResty 配合 Lua 脚本的使用
开发语言·lua·openresty
IT灰猫2 小时前
C++轻量级配置管理器升级版
开发语言·c++·设计模式·配置管理·ini解析
Swift社区2 小时前
如何解决 Vue2 前端项目为何无法访问本地资源(chunk.js 加载一直 pending/转圈)
开发语言·前端·javascript
夜流冰2 小时前
工程师 - Onion Architecture in Software Development
笔记
大飞pkz2 小时前
【设计模式】题目小练2
开发语言·设计模式·c#·题目小练
啟明起鸣3 小时前
【网络编程】从与 TCP 服务器的对比中探讨出 UDP 协议服务器的并发方案(C 语言)
服务器·c语言·开发语言·网络·tcp/ip·udp
007php0073 小时前
Redis高级面试题解析:深入理解Redis的工作原理与优化策略
java·开发语言·redis·nginx·缓存·面试·职场和发展
九章云极AladdinEdu3 小时前
深度学习优化器进化史:从SGD到AdamW的原理与选择
linux·服务器·开发语言·网络·人工智能·深度学习·gpu算力
axban3 小时前
QT M/V架构开发实战:QStandardItemModel介绍
开发语言·数据库·qt