线性表(一)顺序表
一、线性表(linear list)的定义
一个线性表是n个数据元素的有限序列。
注:
1) 数据元素是数据结构的基本单位,可以是一个数或一个符号,也可以是一本书甚至更复杂的信息。在稍复杂线性表中,一个数据元素可以由若干数据项组成。
2) 同一线性表中的元素必定具有相同特性,即属于同一数据对象(类型相同)
二、顺序表C语言实现
这里我们使用库函数malloc创建一个动态数组,指定元素类型和分配空间大小后,malloc会从空闲链表中找到一块合适大小的内存块。这里分配的内存块(连续内存区域)就是我们的数组存放的地方。然后我们把数组的首地址和容量存到一个struct结构体里,并给这个结构体一个别名,就叫SqList
注意顺序表结构的定义,和基本操作(共计12个)的方法定义,统统放进h头文件中,具体方法的实现放到c文件中。
大部分基本操作还是很简单,其中要注意的是destroy方法中释放数组空间后,需要将首地址的指针置空。
最复杂的是ListInsert方法,因为在插入新元素前需要进行容量判断,如果不够需要进行扩容,主要是通过realloc进行内存重新分配。
SqList.h
#define LIST_INIT_SIZE 100 // 顺序表存储空间的初始分配量 #define LISTINCREMENT 10 // 顺序表存储空间的分配增量 /* 顺序表元素类型定义 */ typedef int ElemType; /* * 顺序表结构 * */ typedef struct { ElemType* elem; // 指向顺序表所占内存的起始位置 int length; int listsize; } SqList; Status InitList(SqList* L); Status DestroyList(SqList* L); Status ClearList(SqList* L); Status ListEmpty(SqList L); int ListLength(SqList L);
Status GetElem(SqList L, int i, ElemType* e); int LocateElem(SqList L, ElemType e, Status(Compare)(ElemType, ElemType)); Status PriorElem(SqList L, ElemType cur_e, ElemType* pre_e); Status NextElem(SqList L, ElemType cur_e, ElemType* next_e); Status ListInsert(SqList* L, int i, ElemType e); Status ListDelete(SqList* L, int i, ElemType* e); void ListTraverse(SqList L, void (Visit)(ElemType));
SqList.c
#include "SqList.h"
Status InitList(SqList* L) { // 分配指定容量的内存,如果分配失败,则返回NULL (*L).elem = (ElemType*) malloc(LIST_INIT_SIZE * sizeof(ElemType)); if((*L).elem == NULL) { // 存储内存失败 exit(OVERFLOW); } (*L).length = 0; // 初始化顺序表长度为0 (*L).listsize = LIST_INIT_SIZE; // 顺序表初始内存分配量 return OK; // 初始化成功 } Status DestroyList(SqList* L) { // 确保顺序表结构存在 if(L == NULL || (*L).elem == NULL) { return ERROR; } // 释放顺序表内存 free((*L).elem); // 释放内存后置空指针 (*L).elem = NULL; // 顺序表长度跟容量都归零 (*L).length = 0; (*L).listsize = 0; return OK; } Status ClearList(SqList* L) { // 确保顺序表结构存在 if(L == NULL || (*L).elem == NULL) { return ERROR; } (*L).length = 0; return OK; } Status ListEmpty(SqList L) { return L.length == 0 ? TRUE : FALSE; } int ListLength(SqList L) { return L.length; } Status GetElem(SqList L, int i, ElemType* e) { // 因为i的含义是位置,所以其合法范围是:[1, length] if(i < 1 || i > L.length) { return ERROR; //i值不合法 } *e = L.elem[i - 1]; return OK; } int LocateElem(SqList L, ElemType e, Status(Compare)(ElemType, ElemType)) { int i; ElemType* p; // 确保顺序表结构存在 if(L.elem == NULL) { return ERROR; } /* * i的初值为第1个元素的位序 */ i = 1; // p的初值为第1个元素的存储位置 p = L.elem; // 遍历顺序表 while(i <= L.length && !Compare(*p++, e)) { ++i; } if(i <= L.length) { return i; } else { return 0; } } Status PriorElem(SqList L, ElemType cur_e, ElemType* pre_e) { int i; // 确保顺序表结构存在,且最少包含两个元素 if(L.elem == NULL || L.length < 2) { return ERROR; } // 这里的i初始化为第1个元素的【索引】 i = 0; // 从第1个元素开始,查找cur_e的位置 while(i < L.length && L.elem[i] != cur_e) { ++i; } // 如果cur_e是首个元素(没有前驱),或者没找到元素cur_e,返回ERROR if(i==0 || i >= L.length) { return ERROR; } // 存储cur_e的前驱 *pre_e = L.elem[i - 1]; return OK; }
Status NextElem(SqList L, ElemType cur_e, ElemType* next_e) { int i; // 确保顺序表结构存在,且最少包含两个元素 if(L.elem == NULL || L.length < 2) { return ERROR; } // 这里的i初始化为第1个元素的【索引】 i = 0; // 从第1个元素开始,查找cur_e的位置 while(i < L.length-1 && L.elem[i] != cur_e) { ++i; } // 如果cur_e是最后1个元素(没有前驱),或者没找到元素cur_e,返回ERROR if(i >= L.length-1) { return ERROR; } // 存储cur_e的前驱 *next_e = L.elem[i + 1]; return OK; } Status ListInsert(SqList* L, int i, ElemType e) { ElemType* newbase; ElemType* p, * q; // 确保顺序表结构存在 if(L == NULL || (*L).elem == NULL) { return ERROR; } // i值越界 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 == NULL) { // 存储内存失败 exit(OVERFLOW); } // 新基址 (*L).elem = newbase; // 存的存储空间 (*L).listsize += LISTINCREMENT; } // q为插入位置 q = &(*L).elem[i - 1]; // 1.右移元素,腾出位置 for(p = &(*L).elem[(*L).length - 1]; p >= q; --p) { *(p + 1) = *p; } // 2.插入e *q = e; // 3.表长增1 (*L).length++; return OK; } Status ListDelete(SqList* L, int i, ElemType* e) { ElemType* p, * q; // 确保顺序表结构存在 if(L == NULL || (*L).elem == NULL) { return ERROR; } // i值越界 if(i < 1 || i > (*L).length) { return ERROR; } // p为被删除元素的位置 p = &(*L).elem[i - 1]; // 1.获取被删除元素 *e = *p; // 表尾元素位置 q = (*L).elem + (*L).length - 1; // 2.左移元素,被删除元素的位置上会有新元素进来 for(++p; p <= q; ++p) { *(p - 1) = *p; } // 3.表长减1 (*L).length--; return OK; }
void ListTraverse(SqList L, void(Visit)(ElemType)) { int i; for(i = 0; i < L.length; i++) { Visit(L.elem[i]); } printf("\n"); }
三、A=A∪B
/* * * A=A∪B * * 计算La与Lb的并集并返回。 * 由于生成的并集会拼接在La上,所以La的入参为指针类型。 */ void Union(SqList* La, SqList Lb) { int La_len, Lb_len; int i; ElemType e; // 求顺序表长度 La_len = ListLength(*La); Lb_len = ListLength(Lb); for(i = 1; i <= Lb_len; i++) { // 取Lb中第i个元素赋给e GetElem(Lb, i, &e); // 若e不在La中则插入 if(!LocateElem(*La, e, equal)) { ListInsert(La, ++La_len, e); } } } /* * 判等 * * 判断两元素是否相等。 * 如果相等,则返回TRUE,否则,返回FALSE。 */ Status equal(ElemType e1, ElemType e2) { return e1 == e2 ? TRUE : FALSE; }
四、C=A∪B(A、B有非递减,求C仍保持非递减)
用整形变量 i 和 j 分别代表A和B在遍历中的当前位置,将两个表同时从第一个位置开始向后遍历,每次获取到A和B的元素进行比较,较小的元素插入表C
遍历的元素被选中插入C,则这个表继续往下遍历,否则仍停留在这个位置
这样两个表是同时开始,但是以不同的速度往后推进遍历,其中有一个表会先遍历完。比如表A先遍历完了,那么此时的 i 必然已经大于La_len,而 j 必然还小于等于Lb_len,此时单独用一个循环取出B中所有元素插入C
因为GetElem的时间复杂度是O(1),而ListInsert因为每次都是在最后一个位置插入,所以时间复杂度也是O(1)
所以整体时间复杂度是O(La_len + Lb_len)
void MergeSqList(SqList La, SqList Lb, SqList* Lc) { int La_len, Lb_len; int i, j, k; ElemType ai, bj; i = j = 1; k = 0; // 初始化Lc InitList(Lc); // 获取La、Lb的长度 La_len = ListLength(La); Lb_len = ListLength(Lb); // 如果La及Lb均未遍历完 while(i <= La_len && j <= Lb_len) { GetElem(La, i, &ai); GetElem(Lb, j, &bj); // 比较遍历到的元素,先将比较小的元素加入顺序表Lc if(ai <= bj) { ListInsert(Lc, ++k, ai); i++; } else { ListInsert(Lc, ++k, bj); j++; } } // 如果Lb已遍历完,但La还未遍历完,将La中剩余元素加入Lc while(i <= La_len) { GetElem(La, i++, &ai); ListInsert(Lc, ++k, ai); } // 如果La已遍历完,但Lb还未遍历完,将Lb中剩余元素加入Lc while(j <= Lb_len) { GetElem(Lb, j++, &bj); ListInsert(Lc, ++k, bj); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通