数据结构--线性表的顺序实现以及链式实现(c语言)

# 线性表的定义和特点:

    * 线性表(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

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值