数据结构 顺序表

发布于:2022-10-18 ⋅ 阅读:(699) ⋅ 点赞:(0)

目录

一.线性表

二.顺序表的实现

1.基础知识

2.代码实现

(1)顺序表初始化

(2)头插,尾插,按位置插

 (3)头删,尾删,按位置删

 (4)查找,按值删

(5)判空,判满,扩容,清空,销毁,打印

三.顺序表的优缺点


 

一.线性表

线性表(linear_list)是最常用且最简单的一种数据结构。一个线性表是n个数据元素的有限序列。至于每个数据元素的具体含义,在不同的情况下各不相同,它可以是一个数或一个符号,也可以是一页书,甚至其他更复杂的信息。

     再稍复杂的线性表中,一个数据元素可以由若干个数据项(item)组成。在这种情况下,常把数据元素成为记录(record),含有大量记录的线性表又称文件(file)。

   

有且只有一个开始的结点,并且除了开始结点外,其余结点都有直接前驱,并且除了结束外,其余结点都有直接后继。 

线性表按照其数据的存储方式分为两种:

1.顺序表:数据结点之间,逻辑相邻,物理也相邻

2.链表:数据结点之间,逻辑相邻,物理不一定相邻

二.顺序表的实现

1.基础知识

将逻辑上相邻的数据元素,存储在物理上也相邻的存储单元中。用一组地址连续的存储单元依次存储线性表的数据元素。

2.代码实现

 头文件的设计:

#pragma once
//可扩容的顺序表的结构体设计
#define LIST_INIT_SIZE 100  //初始大小
#define LISTINCREMENT 10

typedef int ELEM_TYPE;

typedef struct Sqlist {
	ELEM_TYPE* elem;//存储空间基址(用来接收malloc返回值在堆上申请的连续空间块开始地址)
	int length;
	int listsize;
}Sqlist,*PSqlist;

//初始化
void Init_Sqlist(struct Sqlist*sq);

//头插
bool Insert_head(struct Sqlist* sq,ELEM_TYPE val);

//尾插
bool Insert_tail(struct Sqlist* sq,ELEM_TYPE val);

//按位置插
bool Insert_pos(struct Sqlist* sq,int pos, ELEM_TYPE val);

//头删
bool Del_head(struct Sqlist* sq);

//尾删
bool Del_tail(struct Sqlist* sq);

//按位置删
bool Del_pos(struct Sqlist* sq,int pos);

//按值删
bool Del_val(struct Sqlist* sq, int val);

//判空
bool IsEmpty(struct Sqlist* sq);

//判满
bool IsFull(struct Sqlist* sq);

//扩容函数
void Inc(struct Sqlist *sq);

//查找
int Search(struct Sqlist* sq,ELEM_TYPE val);

//清空
void Clear(struct Sqlist* sq);

//销毁
void Destory(struct Sqlist* sq);

//打印
void Show(struct Sqlist* sq);

功能实现:

(1)顺序表初始化

首先申请一段连续的内存空间用于构建顺序表,并对头结点成员赋值

#define LIST_INIT_SIZE  100

//初始化  对头节点里面的成员赋值
void Init_Sqlist(struct Sqlist* sq) {
	sq->elem = (ELEM_TYPE*)malloc(LIST_INIT_SIZE*sizeof(int));//申请内存
	assert(sq->elem != NULL);//安全性处理
	sq->length = 0;//初始有效数据个数赋值为0
	sq->listsize = LIST_INIT_SIZE;//长度为初始长度(申请的内存空间)
}

(2)头插,尾插,按位置插

若顺序表已满,则需要进行扩容之后在进行添加数据;若未满则直接进行操作

//头插
bool Insert_head(struct Sqlist* sq, ELEM_TYPE val) {
	//1.判断指针sq是否为NULL
	assert(sq->elem!=NULL);
	//2.判满,满了扩容
	if (IsFull(sq)) {
		Inc(sq);
	}
	//3.向后挪动元素
	for (int i = sq->length-1; i > 0;i--) {
		sq->elem[i+1] = sq->elem[i];
	}
	//4.插入有效值
	sq->elem[0] = val;
	//5.length+1
	sq->length++;
	return true;
}

//尾插
bool Insert_tail(struct Sqlist* sq, ELEM_TYPE val) {
	//1.判断指针sq是否为NULL
	assert(sq!=NULL);
	//2.判满,满了扩容
	if (IsFull(sq)) {
		Inc(sq);
	}
	//3.插入有效值
	sq->elem[sq->length] = val;
	//4.length+1
	sq->length++;
	return true;
}

//按位置插
bool Insert_pos(struct Sqlist* sq, int pos, ELEM_TYPE val) {
	//将插入位置元素和后面的元素整体向后挪动
	//1.判断指针sq是否为NULL
	assert(sq!=NULL);
	//2.判满,满了则扩容
	if (IsFull(sq)) {
		Inc(sq);
	}
	//3.向后挪动元素
	for (int i = sq->length - 1; i >= pos;i++) {
		sq->elem[i+1] = sq->elem[i];
	}
	//4.插入有效值
	sq->elem[pos] = val;
	//5.length+1
	sq->length++;
	return true;
}

(3)头删,尾删,按位置删

若顺序表为空,则不需要进行删除;若不为空,则删除 

//头删
bool Del_head(struct Sqlist* sq) {
	//插入需要判满,删除需要判空
	assert(sq!=NULL);
	//1.判空
	if (IsEmpty(sq)) {
		return false;
	}
	//2.待删元素后面的值向前覆盖
	for (int i = 0; i < sq->length-1;i++) {
		sq->elem[i] = sq->elem[i+1];
	}
	//3.length-1
	sq->length--;
	return true;
}

//尾删
bool Del_tail(struct Sqlist* sq) {
	//安全性处理
	assert(sq!=NULL);
	//1.判空
	if (IsEmpty(sq)) {
		return false;
	}
	//2.删除(有效长度减一)
	sq->length--;
	return true;
}

//按位置删
bool Del_pos(struct Sqlist *sq, int pos) {
	//1.安全性处理 断言(判断sq指针不指向NULL)
	assert(sq!=NULL);
	//2.判断pos的合法性  pos为0时为头删
	if (pos>=0&&pos<sq->length) {
		return false;
	}
	//3.判空 (判断sq指向的不是一个空的顺序表)
	if (IsEmpty(sq)) {
		return false;
	}
	//4.向前覆盖
	for (int i = pos; i < sq->length-1; i++) {
		sq->elem[i]=sq->elem[i+1];
	}
	//5.length-1
	sq->length--;
	return true;
}

(4)查找,按值删

按值删除元素时,需要确定该值所在的数据在什么位置,得到该数据的位置之后,进行按位置删即可。 

//查找
int Search(struct Sqlist* sq, ELEM_TYPE val) {
	//查找这个元素在顺序表中第一次出现的位置
	//遍历
	assert(sq!=NULL);
	for (int i = 0; i < sq->length;i++) {
		if (sq->elem[i] == val) {
			return i;
		}
	}
	return -1;
}
//按值删(这个值在顺序表中第一次出现的位置,然后删除)
bool Del_val(struct Sqlist* sq, int val) {
	//1.断言  安全性处理
	assert(sq!=NULL);
	//2.查找元素所在的位置  找到返回下标  没找到返回-1
	int tmp = Search(sq, val);
	if (tmp == -1) {
		return false;
	}
	//3.按位置删
	return Del_pos(sq, tmp);
}

(5)判空,判满,扩容,清空,销毁,打印

在顺序表的增删过程中,由于存在数据已满或者数据为空的情况,在此基础上进行增删数据,程序容易崩溃。所以在进行增删数据操作之前,要进行判空,判满等安全性处理。

在删除数据时,若已经为空,则不需要进行删除;增加数据时,若已满,则需要进行扩容。

//判空
bool IsEmpty(struct Sqlist* sq) {
	assert(sq!=NULL);
	//有效长度是否为0
	return sq->length == 0;
}

//判满
bool IsFull(struct Sqlist* sq) {
	assert(sq!=NULL);
	//有效长度和顺序表总容量进行比较
	return sq->length == sq->listsize;
}

//扩容函数
void Inc(struct Sqlist* sq) {
	sq->elem =(ELEM_TYPE*)realloc(sq->elem,(sq->listsize*sizeof(int)*2));
	assert(sq!=NULL);
	//按两倍扩容
	sq->listsize *= 2;
}

//清空
void Clear(struct Sqlist* sq) {
	//有效长度为0
	sq->length = 0;
}

//销毁
void Destory(struct Sqlist* sq) {
	//将数据存储空间都释放掉
	free(sq->elem);
	sq->length = 0;
	sq->listsize = 0;
}

//打印
void Show(struct Sqlist* sq) {
	//遍历
	for (int i = 0; i < sq->listsize;i++) {
		printf("%d", sq->elem[i]);
	}
	printf("\n");
}

三.顺序表的优缺点

顺序表的操作需要挪动元素,插入的话,需要向后挪动,删除的话,需要向前覆盖,时间复杂度O(n),效率不高。但是,如果在尾部(尾删尾插)操作,则效率极高为O(1),因为尾部操作不需要挪动元素。

顺序表使用的场景:

适合的场景:支持直接访问;在尾部的插入和删除

不适合的场景:在头部和中间位置,插入和删除操作较多;频繁的扩容,扩容代价高。 


网站公告

今日签到

点亮在社区的每一天
去签到