# 线性表的定义和特点:
* 线性表(Linear List):由n个数据特性相同的元素构成的有限序列,这样的序列称为一个“线性表”;
* 线性表的特点:存在唯一的元素称为“第一个元素”,存在唯一的元素称为“最后一个元素”,除了第一个元素其他的元素都有一个“直接前驱”,除了“最后一个元素”其他的元素都有一个直接后继;
* 线性表的抽象数据类型:(ADT只是一个模型的定义,并没有实现,因此这里的参数不必考虑具体类型;对于不同的实现可以有不同的接口)
ADT List{
数据对象:D = {a1,a2,a3...,an}所有的元素都属于同一个数据类型,且有序有限
数据关系:R = {<a,b>, <b,c>, <c,d>}是一个前驱后继的关系
数据操作:
InitList(&L): 初始化一个空的线性表
DestroyList(&L): 销毁线性表
ClearList(&L): 清空线性表
ListLength(L): 返回线性表中元素的个数
ListEmpty(L): 判断线性表是否为空
GetElem(L,i,&e): 获取L表中下标i上的元素
LocateElem(L,e): 定位线性表中的元素e,返回它的下标,失败返回-1
PriorElem(L,e,&e): 获取L中e前面的元素,失败返回-1
NextElem(L,e,&e): 获取L中e后面的元素,失败返回-1
ListInsert(&L,i,e): 在L的i位置上插入元素e
ListDelete(&L,i): 删除L在i位置上的元素
TraverseList(L): 遍历线性表L
} ADT List;
抽象数据类型对应Java中的抽象类,数据对象与元素之间的关系,可以通过成员变量来存储和实现,数据操作可以通过抽象方法来表示;
* 顺序表:用顺序存储的方式实现线性表,在逻辑上相邻的元素在物理上也相邻;
* 链表:用链式存储的方式实现线性表,在逻辑上相邻的元素在物理上不一定相邻;
----------------------------------------------------------------------------------------------------------
# 顺序表的实现(代码见下方SqList.c):
* 顺序表就是使用一组物理上“地址连续”的存储单元来依次存储线性表中的元素,以元素在计算机内部物理地址的连续性来表示
数据元素之间的关系;它是一种“随机存取”的数据结构;
* 由于高级语言中的数组类型也有随机存储的特性,故一般用数组来实现顺序表,因为数组类型就是一块物理上连续的存储空间;
但是由于线性表的长度可变,但数组长度不可变,所以通常在c中用动态分配的一维数组来实现顺序表:
#define MAXSIZE 100 //可根据需要来调整,数组初始化的大小
#define ElemType int //可根据需要来调整为想要的,顺序表类型
typedef struct{
//在顺序表初始化函数中,会将该指针指向顺序表的基地址,大小为MAXSIZE
ElemType *elem;
int length; //记录顺序表的长度
}SqList; //可以通过:SqList.elem[i]来获取顺序表中的元素
* 顺序表的特点:
优点:支持“随机存取”,存储密度高
缺点:随机增删元素很不方便,且扩容不方便
应用场景:适合存储,当元素的插入/删除操作比较少,查询操作较多时;
复杂度:按位查找和修改的时间复杂度为O(1),按值查找和修改的时间复杂度为O(n);随机增删复杂度为O(n),但效率较低;
----------------------------------------------------------------------------------------------------------
# 链表的实现(代码见下方SLinkedList.c):
* 链表就是用一组任意的存储单元存储线性表中的元素,这组存储单元可以连续也可以不连续;为了表示每个元素与其后继元素之间的关系,
链表上的每个结点node都至少有两个域,“数据域”和“指针域”;链表本身也是一种递归的数据结构;
* 单链表:单向链表的每个结点至少有两个域,“数据域”用来存储数据,“指针域”指向下个结点的地址;
优点:不需要连续的存储空间,没有初始容量且扩容方便;
缺点:存储密度低,且不支持随机访问;
* 双链表:
单链表只能通过结点的指针域来访问它的后继结点,不能访问改结点的前驱结点,如果要找某个结点的前驱结点,
就需要从表头结点依次开始找;故为了方便对前驱结点的访问,于是有了“双链表”;
双链表中,扩展了结点的结构,每个结点除了存储数据外,还分别有两个指针域用来存储前驱结点和后继结点的地址;
* 循环链表:单链表的最后一个结点next指向了头结点,形成了循环;
(还有特殊的:二叉链表、十字链表、邻接表、临接多重表等,一般用于实现非线性结构)
(
还有一种特殊的线性表的实现方式:
* 静态链表:用数组的方式来定义链表,数组中每个位置存放值和下一个结点的下标,一般是结构体数组
优点:增删不需要移动大量的元素,只需要修改指针域的值
缺点:容量不可变;需要连续的存储空间,存储密度低,且不支持随机访问
应用场景:某些不支持指针的低级语言,可能需要用数组的方式来实现单链表
)
----------------
线性表的顺序存储和链式存储的比较:
时间上的比较:
顺序表可以直接通过索引值访问每个元素,实现了表中元素的随机访问;链表每次从头结点开始查,效率较低;
顺序表在随机增删时需要移动大量的元素,而链表随即增删只需要移动前后驱指针即可,不需要很多移动元素
所以如果是查询操作,优先顺序表,如果是随机增删,优先链表
空间上的比较:
顺序表需要预先分配一块连续的存储空间,在使用过程中会出现闲置的空间,并且无法存储非常大的数据;而链表的空间是动态分配的,磁盘的
利用率较高,不会浪费空间,并且如果线性表的长度经常变化的话,优先选择链式存储;如果长度变化不大的话,优先选择顺序表,因为链式
存储需要额外的空间来存储前驱和后继,且存储密度低;
***************************************************************************************************
SqList.c:
#define ElemType int
#define MAXSIZE 10
#include <stdlib.h>
#include <stdio.h>
typedef struct{
ElemType* elem; //数组基地址
int capacity; //表最大容量
int length; //表长
}SqList;
// 空指针异常,用于判断表指针是否为NULL
static void NullPointerException(SqList *list){
if(list == NULL){exit(1);}
}
//顺序表扩容(2倍扩容)
static void ExpandSize(SqList *list){
// 首先进行指针判空
NullPointerException(list);
// 临时指针old,保存旧表的基地址
ElemType* old = list->elem;
// 申请一片新的空间,是原容量的2倍
// 先设置表容量为原来的2倍
list->capacity *= 2;
// 直接将表指针指向新容量的地址
list->elem = (ElemType*)malloc(list->capacity * sizeof(ElemType));
// 如果申请失败,程序直接终止
if(list->elem == NULL){exit(1);}
// 申请成功,将旧表的内容进行拷贝
int i;
for(i=0; i<list->length; i++){
list->elem[i] = old[i];
}
// 销毁旧表
free(old);
}
// InitList(&L): 初始化一个空的顺序表,默认容量为MAXSIZE
void InitList(SqList* list){
// 首先进行指针判空
NullPointerException(list);
// malloc动态申请一片连续的空间,让顺序表指向这片地址
list->elem = (ElemType*)malloc(MAXSIZE * sizeof(ElemType));
// 如果申请失败,此时程序异常终止
if(list->elem == NULL){exit(1);}
// 申请成功,初始表中的其他属性
list->length = 0;
list->capacity = MAXSIZE;
}
// DestroyList(&L): 销毁顺序表
void DestroyList(SqList* list){
// 首先进行指针判空
NullPointerException(list);
// 销毁顺序表
free(list->elem);
// 顺序表销毁后,表指针置空,避免已经回收的内存再次被访问
list->elem = NULL;
list->length = 0;
list->capacity = 0;
}
// ClearList(&L): 清空顺序表
void ClearList(SqList* list){
// 首先进行指针判空
NullPointerException(list);
list->length = 0;
}
// ListEmpty(L): 判断顺序表是否为空,空返回1
int ListEmpty(SqList list){
return list.length == 0;//这里只访问顺序表中的length属性,不对它进行修改,所以函数参数不需要设置为指针
}
// GetElem(L,i,&e): 获取L表中下标i上的元素
ElemType GetElem(SqList list, int i){
ElemType ret;
if(i >= list.length || i < 0){ //下标非法
exit(1);
}else { //合法
ret = list.elem[i];
}
return ret;
}
// LocateElem(L,e): 定位顺序表中元素e的下标,失败返回-1
//顺序表按值查找的时间复杂度为:O(n)
int LocateElem(SqList list, ElemType e){
int ret = -1;
int i;
for(i=0; i<list.length; i++){
if(list.elem[i] == e){
ret = i;
break;
}
}
return ret;
}
//获取表长
int GetSize(SqList list){
return list.length;
}
// ListInsert(&L,i,e): 在L的下标i位置上插入元素e,成功返回下标i,失败返回-1
// 顺序表随即插入或删除的时间复杂度为:O(n)
int ListInsert(SqList* list, int i, ElemType e){
// 首先进行指针判空
NullPointerException(list);
int ret = i;
if(i > list->length || i < 0){ //下标越界,返回-1
ret = -1;
}else {
// 判断表容量是否充足,不够就扩容
if(list->length == list->capacity){
ExpandSize(list);
}
//容量充足或扩容完毕,开始插入
int j;
for(j = list->length; j > i; j--){
list->elem[j] = list->elem[j-1];
}
list->elem[i] = e;
list->length ++;
}
return ret;
}
// ListDelete(&L,i): 删除L在i下标上的元素,成功返回下标i,失败返回-1
// 顺序表随即插入或删除的时间复杂度为:O(n)
int ListDelete(SqList* list, int i){
// 首先进行指针判空
NullPointerException(list);
int ret = i;
//下标越界返回-1
if(i >= list->length || i < 0){
ret = -1;
}else {
//删除
int j;
for(j = i; j<list->length-1; j++){
list->elem[j] = list->elem[j+1];
}
list->length --;
}
return ret;
}
// PriorElem(L,e,&e): 获取L中e前面的元素
ElemType PriorElem(SqList list, ElemType e){
ElemType ret;
int i = LocateElem(list, e);
if( i>0 ){
ret = list.elem[i-1];
}else{ //元素e不存在或e是第一个元素前面没有东西,结束程序
exit(1);
}
return ret;
}
// NextElem(L,e,&e): 获取L中e后面的元素
ElemType NextElem(SqList list, ElemType e){
ElemType ret;
int i = LocateElem(list, e);
if( i >= 0 && i < list.length-1){
ret = list.elem[i+1];
}else{ //不存在e或是最后一个元素,结束程序
exit(1);
}
return ret;
}
// TraverseList(L): 遍历L上各元素值
void TraverseList(SqList list){
printf("\n");
int i;
for(i=0; i<list.length; i++){
printf("%d ", list.elem[i]);
}
printf("\n");
}
// 顺序表拷贝,参数依次为:源表地址,目的表地址,源表开始处,目的表开始处,拷贝长度;拷贝成功返回1,失败返回0
int ListCopy(SqList *sList, SqList *dList, int sStart, int dStart, int size){
// 首先进行指针判空
NullPointerException(sList);
NullPointerException(dList);
// 检查下标是否合法
if(! ((sList->length)-sStart >= size && (dList->capacity)-dStart <= size) ){return 0;}
// 合法则开始拷贝
int i;
for(i=0; i<size; i++){
dList->elem[dStart+i] = sList->elem[sStart+i];
}
return 1;
}
//=============测试程序==============================================================
int main(int argc, char const *argv[])
{
// 定义一个顺序表并初始化
SqList list;
InitList(&list);
//添加元素
ListInsert(&list, 0, 12);
ListInsert(&list, 1, 22);
ListInsert(&list, 2, 32);
ListInsert(&list, 3, 42);
printf("3下标上的元素为:%d\n", GetElem(list, 3));
printf("元素32的下标为:%d\n", LocateElem(list, 32));
ListInsert(&list, 2, 31);
ListDelete(&list, 1);
printf("31前面元素为:%d\n", PriorElem(list, 31));
printf("31后面元素为:%d\n", NextElem(list, 31));
TraverseList(list);
printf("表长为:%d,表容量为:%d\n", GetSize(list), list.capacity);
printf("顺序表是否为空:%d\n", ListEmpty(list));
ClearList(&list);
printf("顺序表是否为空:%d\n", ListEmpty(list));
printf("=================================================================\n");
// 最后一定要销毁顺序表
DestroyList(&list);
printf("顺序表是否为空:%d\n", ListEmpty(list));
return 0;
}
运行结果:
********************************************************************************************************
单链表SLinkedList.c:
#define ElemType int
#include <stdlib.h>
#include <stdio.h>
typedef struct Node{ //单链表结点Node
ElemType elem; //数据域
struct Node *next; //指针域
}Node;
// 这里也可以加一个*SLinkedList,这样就省略了下面的链表结构体的定义,可以用表头的数据域存储表长length,
// 但是还是不好用,因为不知道哪个结点是表尾,在表尾增删不方便;且需要用到双重指针,所以我们加一个链表的结构体,如下所示:
typedef struct SLinkedList{ //单链表SLinkedList
struct Node *head; //链表头
struct Node *tail; //链表尾
int length; //表长length
}SLinkedList;
// 空指针异常,用于判断表指针是否为NULL
static void NullPointerException(SLinkedList *list){
if(list == NULL){exit(1);}
}
//获取链表上第i个结点的指针,没有返回NULL
Node* GetNode(SLinkedList* list, int i){
// 首先进行表指针判空
NullPointerException(list);
if(list->length < i || i <= 0){ //下标非法返回NULL
return NULL;
}else if(i == 1){ //表头结点
return list->head;
}else if(i == list->length){ //表尾结点
return list->tail;
}else{ //表中间的结点
Node* t = list->head->next;
int j;
for(j=2; j<i; j++){
t = t->next;
}
return t;
}
}
// InitList(&L): 初始化单链表
void InitList(SLinkedList* list){
// 首先进行表指针判空
NullPointerException(list);
list->head = NULL;
list->tail = NULL;
list->length = 0;
}
// DestroyList(&L): 销毁单链表
void DestroyList(SLinkedList* list){
// 首先进行表指针判空
NullPointerException(list);
// 空表,什么也不用做
if(list->length==0){
}else{//非空表,层层删除链表结点
Node* t = NULL;
while(list->head->next != NULL){
t = list->head;
list->head = list->head->next;
free(t);
}
free(list->head);
list->head = NULL;
list->tail =NULL;
list->length = 0;
}
}
// ListEmpty(L): 判断单链表是否为空
int ListEmpty(SLinkedList list){
return list.length == 0;
}
// ClearList(&L): 清空线性表
void ClearList(SLinkedList* list){
DestroyList(list);
}
//获取表长
int GetSize(SLinkedList list){
return list.length;
}
// 表头插入元素e
void InsertHead(SLinkedList* list, ElemType e){
// 首先进行表指针判空
NullPointerException(list);
// 新建node
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->elem = e;
newNode->next = list->head;
// 更改表头head
list->head = newNode;
list->length ++;
//如果此时表长为1,令表尾也指向新加的node
if(list->length == 1){
list->tail = newNode;
}
}
// 表尾插入元素e
void InsertTail(SLinkedList* list, ElemType e){
// 首先进行表指针判空
NullPointerException(list);
// 新建node
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->elem = e;
newNode->next = NULL;
// 若表长>0,则更改表尾tail
if(list->length > 0){
list->tail->next = newNode;
list->tail = newNode;
}else{//若为空表
list->head = newNode;
list->tail = newNode;
}
list->length ++;
}
// 删除表头
void DeleteHead(SLinkedList* list){
// 首先进行表指针判空
NullPointerException(list);
// 若为空表,什么也不做
if(list->length == 0){
return;
}else{//表非空
Node* t = list->head;
list->head = list->head->next;
free(t);
list->length --;
//如果此时表为空了,将表尾tail置空
if(list->length == 0){
list->tail = NULL;
}
}
}
// 删除表尾
void DeleteTail(SLinkedList* list){
// 首先进行表指针判空
NullPointerException(list);
// 若为空表,什么也不做
if(list->length == 0){
return;
}else{//表非空
//表长为1时,将表唯一结点删除即可
if(list->length == 1){
free(list->head);
list->head = NULL;
list->tail = NULL;
}else{//表长>1时,需要将尾结点的前驱结点的next置空
Node* t = list->head;
while(t->next!=list->tail){//如果下一个不是尾结点,就一直顺着往下找
t = t->next;
}
//此时t就是尾结点的前驱结点,删除并更新尾结点
t->next = NULL;
free(list->tail);
list->tail = t;
}
// 最后更新表长
list->length --;
}
}
// ListInsert(&L,i,e): 链表的第i个位置上插入元素e
void ListInsert(SLinkedList* list, int i, ElemType e){
// 首先进行表指针判空
NullPointerException(list);
if(i > list->length+1 || i <= 0){ //下标越界,异常退出
exit(1);
}else { //下标合法
if(list->length == 0 || i == 1){//如果是空表,或在表头插入
InsertHead(list, e);
}else if(i == list->length+1){ //表尾添加
InsertTail(list, e);
}else { //表中添加
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->elem = e;
Node* t = list->head;
int j;
for(j=1; j<i-1; j++){
t = t->next;
}
//此时t指向要插入位置的前面
newNode->next = t->next;
t->next = newNode;
list->length ++;
}
}
}
// ListDelete(&L,i): 删除链表第i个位置上的元素并返回
ElemType ListDelete(SLinkedList* list, int i){
// 首先进行表指针判空
NullPointerException(list);
// 先判断下标是否越界
if(i<1 || i>list->length){ exit(1); }
ElemType ret;
if(i == 1){ //删表头
ret = list->head->elem;
DeleteHead(list);
}else if(i == list->length){//删表尾
ret = list->tail->elem;
DeleteTail(list);
}else{ //删表中间
Node* priorNode = GetNode(list, i-1);
ret = priorNode->next->elem;
Node* t = priorNode->next;
priorNode->next = priorNode->next->next;
free(t);
list->length --;
}
return ret;
}
// 前插
// void PriorInsert(SLinkedList* list, Node* node, ElemType e){//将元素e前插到node处
// Node* newNode = (Node*) malloc(sizeof(Node));
// newNode->elem = node->elem;
// node->elem = e;
// newNode->next = node->next;
// node->next = newNode;
// list->length ++;
// if(list->tail == node){
// list->tail = newNode;
// }
// }
// 后插
// void NextInsert(SLinkedList* list, Node* node, ElemType e){//将元素e后插到node处
// Node* newNode = (Node*) malloc(sizeof(Node));
// newNode->elem = e;
// newNode->next = node->next;
// node->next = newNode;
// list->length ++;
// if(list->tail == node){
// list->tail = newNode;
// }
// }
// GetElem(L,i,&e): 获取L表中下标i上的元素
ElemType GetElem(SLinkedList* list, int i){
return GetNode(list, i)->elem;
}
//LocateElem(L,e):定位表中的元素e,看它是第几个元素,失败返回-1
int LocateElem(SLinkedList list, ElemType e){
int ret = -1;
Node* t = list.head;
int i;
for(i=1; i<=list.length; i++){
if(t->elem == e){
ret = i;
break;
}else{
t = t->next;
}
}
// while(t != NULL){
// i++;
// if (t->elem == e) {
// return i;
// }else{
// t = t->next;
// }
// }
return ret;
}
//遍历链表
void TraverseList(SLinkedList list){
Node* t = list.head;
printf("\n");
while(t != NULL){
printf("%d ", t->elem);
t = t->next;
}
printf("\n");
}
//=============测试程序==============================================================
int main(int argc, char const *argv[])
{
// 定义一个链表并初始化
SLinkedList list;
InitList(&list);
// 添加元素
InsertHead(&list, 33);
InsertHead(&list, 23);
InsertHead(&list, 13);
printf("第2个元素为:%d\n", GetElem(&list, 2));
printf("元素33是表中第%d个\n", LocateElem(list, 33));
InsertTail(&list, 43);
ListInsert(&list, 3, 333);
TraverseList(list);
ListDelete(&list, 3);
printf("\n链表是否为空:%d\n", ListEmpty(list));
DeleteHead(&list);
DeleteTail(&list);
TraverseList(list);
printf("表长为:%d\n", GetSize(list));
ClearList(&list);
printf("链表是否为空:%d\n", ListEmpty(list));
// 最后一定要销毁链表
DestroyList(&list);
printf("链表是否为空:%d\n", ListEmpty(list));
return 0;
}
运行结果:
------------------------
代码还待完善,欢迎评论指正O.O