数据结构---顺序表全功能实现

顺序表的基本功能:增删查改
我将对每一个接口的功能及实现方法进行介绍
顺序表的结构与数组相似,都可以通过对下标的访问,访问到数据

结构体的创建
首先定义一个结构体类型,对属性进行添加,我的结构体中定义了一个地址(用来存放动态开辟空间的地址),如果需要的是简单的静态顺序表,可以将指针换做数组使用。接着定义一个capacity,表示容量(用于动态开辟内存的大小),一个size用于记录内存中有效数据的个数。第一个typdef的作用是:将数据类型进行重命名,在改变数据的类型时,将int改变即可。第二个typdef的作用:在C语言中在使用结构体变量时,必须加上struct,为了方便起见,将struct SeqList重命名为SeqList使用,效果与原来是等价的。最后创建了一个结构体类型的全局变量,为了方便能在整个代码中使用,创建临时变量也可。

typedef int SLDataType;

struct SeqList {
	SLDataType *array;	// 指针,指向存放数据的空间,真正的空间在堆上
	int capacity;		// 顺序表整体的容量
	int size;			// 顺序表中真正有效的个数
						// 初始值为 0,同时也表示下一个有效位置的下标
};

typedef struct SeqList	SeqList;
SeqList seqlist;

各个接口的功能及实现

顺序表的初始化/销毁
初始化:将结构体中的各个属性进行赋初值,array的初值为在堆上申请开辟capacity个大小为SLDataType的连续空间的首地址,并将申请到的所有空间初始化为0。

//Init
void SeqListInit(SeqList *sl, size_t capacity)
{
	assert(sl);
	sl->array = (SLDataType*)malloc(sizeof(SLDataType) * capacity);
	for (size_t i = 0; i < capacity; i++)
	{
		sl->array[i] = 0;
	}
	sl->size = 0;
	sl->capacity = capacity;
	return;
}

销毁:将结构体的各个属性变为初值,将堆上申请的空间进行释放。

//销毁
void SeqListDestroy(SeqList *sl)
{
	assert(sl);
	free(sl->array);
	sl->size = 0;
	sl->capacity = 0;
	return;
}

插入
尾插:在顺序表的尾部插入一个数据
在这里插入图片描述

如图所示将4插入下一个空间,每插入一个数据,size就会加1,所以size指向下一个存放数据的位置,所以将需要插入的数据data放入这个位置即可—sl->array[sl->size] = data。在插入结束后必须对size进行更新(sl->size++)。
注意:将数据插入,需要先考虑下一个空间是不是有效空间,如果对无效空间进行赋值就是未定义行为。所以需要一个单独的接口判断是否需要扩容。我们后边会讲到这个接口(CheckCapacity),暂时先使用。

void SeqListPushBack(SeqList *sl, SLDataType data)
{
	assert(sl);
	CheckCapacity(&seqlist);
	sl->array[sl->size] = data;
	sl->size++;
	return;
}

头插:将数据插入到第一个位置
在这里插入图片描述

如图所示,在插入前,我们应该将里面已有的值进行搬移,否则将导致一些值被覆盖丢失,所以应该从后向前将已有的数字进行搬移,再将data插入到下标为0的空间。结束后更新size。
注意:在开始搬移前先对容量进行检查。当有效值为0个时,头插就相当于尾插。

//头插,插入在顺序表的头部
void SeqListPushFront(SeqList *sl, SLDataType data)
{
	assert(sl);
	CheckCapacity(&seqlist);
	for (int i = sl->size; i > 0; i--)
	{
		sl->array[i] = sl->array[i-1];
	}
	sl->array[0] = data;
	sl->size++;
	return;
}

在下标为pos的地方进行插入:将数据插入到下标为pos的空间

在这里插入图片描述
如图所示,在pos(下标为2)的地方插入一个data。类似于头插,先将需要插入的地方空出来,然后将data放入。结束时更新size。
注意:要先对pos的有效范围进行确定,pos应该大于等于0,小于size。应该对异常进行相应的处理,为了方便起见,我选择用assert直接断言,不准输入异常值,否则直接让程序崩溃。仍然应该对容量进行检查。当pos的值等于0时,就是对顺序表进行头插,当pos的值等于size是就是对顺序表进行尾插,

//在pos位置进行插入
void SeqListInsert(SeqList *sl, size_t pos, SLDataType data)
{
	assert(sl);
	assert((int)pos >= 0 && (int)pos <=sl->size);
	CheckCapacity(&seqlist);
	if (pos == 0)
	{
		SeqListPushFront(&seqlist,data);
		return;
	}
	if (pos == sl->size)
	{
		SeqListPushBack(&seqlist,data);
		return;
	}
	for (int i = sl->size; i > (int)pos; i--)
	{
		sl->array[sl->size] = sl->array[sl->size - 1];
	}
	sl->array[pos] = data;
	sl->size++;
	return;
}

检测是否需要扩容
因为是动态开辟的内存,所以我们在插入的时候,必须先判断插入后申请来的内存是否够用,如果够用,不做任何操作,直接返回;如果不够用就需要动态开辟一块更大的内存将原来的数据进行搬移。在每一次插入中都要进行扩容检测的操作。
注意:在申请到新的内存空间,搬移后需要对旧的空间进行及时释放,否则就属于内存泄漏。在释放时记得先对旧的空间进行释放,然后对新地址进行赋值,否则会导致找不到旧的空间了,也属于内存泄漏。用一个新地址记录旧的地址进行释放也可以。开辟结束后要对地址和容量进行更新。

//检测是否需要扩容
void CheckCapacity(SeqList *sl)
{
	assert(sl);
	if (sl->size < sl->capacity)
	{
		return;
	}
	SLDataType newCapacity = sl->capacity * 2;
	SLDataType *newarray = (SLDataType*)malloc(sizeof(SLDataType) * newCapacity);
	if (newarray == NULL)
	{
		printf("内存开辟失败\n");
		return;
	}
	for (int i = 0; i < sl->size; i++)
	{
		newarray[i] = sl->array[i];
	}
	free(sl->array);
	sl->array = newarray;
	sl->capacity = newCapacity;
	newarray = NULL;
	return;
}

删除
尾删:删除顺序表的最后一个数据
在这里插入图片描述
尾删的思路是非常简单的,我只要将最后一个数据认为是无效的数据,就可以完成删除。在顺序表中,有效数据由size进行控制。所以size-1就完成了操作。
注意:当size的值为0时,不能进行删除。可以对这种异常进行相应的处理,为了方便起见,我使用了assert进行断言,不允许size为0的情况下,仍然进行删除。

//尾删
void SeqListPopBack(SeqList *sl)
{
	assert(sl);
	assert(sl->size != 0);
	sl->size--;
	return;
}

头删:删除顺序表的第一个数据
在这里插入图片描述
头删的思路就是将第一个元素后面的元素全部向前移动一个位置,再将顺序表中的有效长度减一(size-1)就完成了头删。
注意:当size的值为0时,不能进行删除。可以对这种异常进行相应的处理,为了方便起见,我使用了assert进行断言,不允许size为0的情况下,仍然进行删除。当顺序表中的元素只有一个时,我在这里调用了尾删的接口,不调用仍然是正确的。

//头删
void SeqListPopFront(SeqList *sl)
{
	assert(sl);
	assert(sl->size != 0);
	if (sl->size == 1)
	{
		SeqListPopBack(&seqlist);
		return;
	}
	for (int i = 0; i < sl->size-1; i++)
	{
		sl->array[i] = sl->array[i + 1];
	}
	sl->size--;
	return;
}

在pos位置进行删除:将下标为pos位置的数据进行删除
在这里插入图片描述
在pos位置删除与在pos位置插入相似,pos位置插入是将pos位置空出来,删除则是将pos位置覆盖掉,所以应该将pos位置后的元素向前移动,然后将有效长度减一(size-1)。
***注意***在顺序表的长度为0时,不允许进行删除操作。当pos的值为0时调用头删操作,当pos的值为size-1时调用尾插操作。

//在pos的位置进行删除
void SeqListErase(SeqList *sl, size_t pos)
{
	assert(sl);
	assert(sl->size != 0);
	assert((int)pos >= 0 && (int)pos < sl->size);
	if (pos == 0)
	{
		SeqListPopFront(&seqlist);
		return;
	}
	if (pos == sl->size - 1)
	{
		SeqListPopBack(&seqlist);
		return;
	}
	for (int i = pos; i < sl->size - 1; i++)
	{
		sl->array[i] = sl->array[i + 1];
	}
	sl->size--;
	return;
}

查找:在顺序表中查找返回第一个查询到的数据下标
查找的思路就是从前向后找,因为在这里顺序表还是无序的,在后面会讲到将顺序表重新排序后,利用二分查找进行查找,可以提高查找的效率。如果找到就返回数据所在的下标,如果找不到就返回-1.

//查找
int SeqListFind(SeqList *sl, SLDataType data)
{
	assert(sl != NULL);
	for (int i = 0; i < sl->size; i++)
	{
		if (sl->array[i] == data)
		{
			return i;
		}
	}
	return -1;
}

删除(数据):根据数据在顺序表中找到第一个出现的进行删除
这个删除需要先进行查找,找到第一个相等的数据后,返回下标,然后根据这个下标删除对应的数据

//(数据)删除---先查找--根据下标删除
void SeqListRemove(SeqList *sl, SLDataType data)
{
	assert(sl);
	size_t ret = SeqListFind(&seqlist, data);
	SeqListErase(&seqlist, ret);
	return;
}

修改:根据下标修改对应位置的数据
修改操作非常简单,给定了下标,就根据这个下标对数据进行更改,需要注意的是要对pos的有效值进行判定,不可以对无效空间进行赋值。

//修改
void SeqListModify(SeqList *sl, size_t pos, SLDataType data)
{
	assert(sl);
	assert((int)pos >= 0 && (int)pos < sl->size);
	sl->array[pos] = data;
	return;
}

冒泡排序:将顺序表排序,为二分查找提供前提
Swap函数被static修饰,表示的是此函数为此源文件私有,不能被其他源文件调用。作用是交换两个数

//冒泡排序,为二分查找做准备
static void Swap(SLDataType *x, SLDataType *y)
{
	SLDataType tmp = *x;
	*x = *y;
	*y = tmp;
	return;
}

void SeqListBubbleSort(SeqList *sl)
{
	assert(sl);
	for (int i = 0; i < sl->size - 1; i++)
	{
		for (int j = 0; j < sl->size - 1 - i; j++)
		{
			if (sl->array[j] > sl->array[j + 1])
			{
				Swap(sl->array + j, sl->array + j + 1);
			}
		}
	}
}

二分查找:在有序顺序表中查找数据,返回数据所在的下标。
二分查找的思路就是找一半,丢一半。比如我们在1–100的数字中,猜一个数(20)。我们第一次猜50,他会反馈猜大了(1–50),第二次猜25,反馈猜大了(1–25),第三次猜13,反馈猜小了(13–25),第四次猜19,反馈猜小了(19–25),第五次猜22,反馈猜大了(19–22),第六次猜20,反馈猜对了。二分查找的思想就是这样,确定一半的范围,丢掉另一半。

int SeqListBanarySearch(SeqList *sl, SLDataType data)
{
	SLDataType left = 0;
	SLDataType right = sl->size - 1;
	SLDataType mid = 0;
	while (left <= right)
	{
		mid = left + ((right - left) >> 1);
		if (sl->array[mid] < data)
		{
			left = mid + 1;
		}
		else if (sl->array[mid] > data)
		{
			right = mid - 1;
		}
		if (sl->array[mid] == data)
		{
			return mid;
		}
	}
	return -1;
}

删除同一个数全部:在顺序表中从前到后将data数据全部删除。
这里介绍一种方法,我觉得是非常好的,在很多情况下可以使用。在用i遍历顺序表时,定义一个j,当i遇到的不是要删除的元素时,j跟随i移动。如果i遇到了要删除的元素时,i向后走,j留在原地,知道i走到不是要删除元素的位置时,将i所在的元素赋给j的下一个位置,最后顺序表的长度就是j的值,更新size的值就完成了删除同一个数全部。
在这里插入图片描述

//(数据)删除同一个数全部
void SeqListRemoveAll(SeqList *sl, SLDataType data)
{
	int j = 0;
	for (int i = 0; i < sl->size; i++)
	{
		if (sl->array[i] != data)
		{
			sl->array[j++] = sl->array[i];
		}
	}
	sl->size = j;
	return;
}

打印:将顺序表中的所有内容打印到屏幕上
这个接口的实现非常简单,遍历整个顺序表即可。

//打印
void SeqListPrint(SeqList *sl)
{ 
	assert(sl);
	for (int i = 0; i < sl->size; i++)
	{
		printf("%d ",sl->array[i]);
	}
	printf("\n");
	return;
}

参考的源代码如下:
SeqList.h:

#pragma once
// Sequence List

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>


typedef int SLDataType;

struct SeqList {
	SLDataType *array;	// 指针,指向存放数据的空间,真正的空间在堆上
	int capacity;		// 顺序表整体的容量
	int size;			// 顺序表中真正有效的个数
						// 初始值为 0,同时也表示下一个有效位置的下标
};

typedef struct SeqList	SeqList;
SeqList seqlist;


// 封装的接口

// 初始化/销毁
void SeqListInit(SeqList *sl, size_t capacity);
void SeqListDestroy(SeqList *sl);

// 增删查改
// 尾插,插入在顺序表的尾部
void SeqListPushBack(SeqList *sl, SLDataType data);

// 头插,插入在顺序表的头部 ([0])
void SeqListPushFront(SeqList *sl, SLDataType data);

// 尾删,删除顺序表尾部的数据
void SeqListPopBack(SeqList *sl);

// 头删,删除顺序表头部的数据
void SeqListPopFront(SeqList *sl);

// 查找
int SeqListFind(SeqList *sl, SLDataType data);

//在pos的位置进行插入
void SeqListInsert(SeqList *sl, size_t pos, SLDataType data);

//在pos的位置进行删除
void SeqListErase(SeqList *sl, size_t pos);

//(数据)删除
void SeqListRemove(SeqList *sl, SLDataType data);

//修改
void SeqListModify(SeqList *sl, size_t pos, SLDataType data);

//冒泡排序
void SeqListBubbleSort(SeqList *sl);

//二分查找
int SeqListBanarySearch(SeqList *sl, SLDataType data);

//(数据)删除同一个数全部
void SeqListRemoveAll(SeqList *sl, SLDataType data);

// 打印
void SeqListPrint(SeqList *sl);

// (内部接口)检测是否需要扩容
void CheckCapacity(SeqList *sl);

sqlist.c:

#include "SeqList.h"

//Init
void SeqListInit(SeqList *sl, size_t capacity)
{
	assert(sl);
	sl->array = (SLDataType*)malloc(sizeof(SLDataType) * capacity);
	for (size_t i = 0; i < capacity; i++)
	{
		sl->array[i] = 0;
	}
	sl->size = 0;
	sl->capacity = capacity;
	return;
}

//销毁
void SeqListDestroy(SeqList *sl)
{
	assert(sl);
	free(sl->array);
	sl->size = 0;
	sl->capacity = 0;
	return;
}

/*
增删查改
*/

// 尾插,插入在顺序表的尾部
void SeqListPushBack(SeqList *sl, SLDataType data)
{
	assert(sl);
	CheckCapacity(&seqlist);
	sl->array[sl->size] = data;
	sl->size++;
	return;
}

//头插,插入在顺序表的头部
void SeqListPushFront(SeqList *sl, SLDataType data)
{
	assert(sl);
	CheckCapacity(&seqlist);
	for (int i = sl->size; i > 0; i--)
	{
		sl->array[i] = sl->array[i-1];
	}
	sl->array[0] = data;
	sl->size++;
	return;
}

//尾删
void SeqListPopBack(SeqList *sl)
{
	assert(sl);
	assert(sl->size != 0);
	sl->size--;
	return;
}

//头删
void SeqListPopFront(SeqList *sl)
{
	assert(sl);
	assert(sl->size != 0);
	if (sl->size == 1)
	{
		SeqListPopBack(&seqlist);
		return;
	}
	for (int i = 0; i < sl->size-1; i++)
	{
		sl->array[i] = sl->array[i + 1];
	}
	sl->size--;
	return;
}

//查找
int SeqListFind(SeqList *sl, SLDataType data)
{
	assert(sl != NULL);
	for (int i = 0; i < sl->size; i++)
	{
		if (sl->array[i] == data)
		{
			return i;
		}
	}
	return -1;
}

//在pos位置进行插入
void SeqListInsert(SeqList *sl, size_t pos, SLDataType data)
{
	assert(sl);
	assert((int)pos >= 0 && (int)pos <=sl->size);
	CheckCapacity(&seqlist);
	if (pos == 0)
	{
		SeqListPushFront(&seqlist,data);
		return;
	}
	if (pos == sl->size)
	{
		SeqListPushBack(&seqlist,data);
		return;
	}
	for (int i = sl->size; i > (int)pos; i--)
	{
		sl->array[sl->size] = sl->array[sl->size - 1];
	}
	sl->array[pos] = data;
	sl->size++;
	return;
}

//在pos的位置进行删除
void SeqListErase(SeqList *sl, size_t pos)
{
	assert(sl);
	assert(sl->size != 0);
	assert((int)pos >= 0 && (int)pos < sl->size);
	if (pos == 0)
	{
		SeqListPopFront(&seqlist);
		return;
	}
	if (pos == sl->size - 1)
	{
		SeqListPopBack(&seqlist);
		return;
	}
	for (int i = pos; i < sl->size - 1; i++)
	{
		sl->array[i] = sl->array[i + 1];
	}
	sl->size--;
	return;
}

//(数据)删除---先查找--根据下标删除
void SeqListRemove(SeqList *sl, SLDataType data)
{
	assert(sl);
	size_t ret = SeqListFind(&seqlist, data);
	SeqListErase(&seqlist, ret);
	return;
}

//修改
void SeqListModify(SeqList *sl, size_t pos, SLDataType data)
{
	assert(sl);
	assert((int)pos >= 0 && (int)pos < sl->size);
	sl->array[pos] = data;
	return;
}

//冒泡排序,为二分查找做准备
static void Swap(SLDataType *x, SLDataType *y)
{
	SLDataType tmp = *x;
	*x = *y;
	*y = tmp;
	return;
}

void SeqListBubbleSort(SeqList *sl)
{
	assert(sl);
	for (int i = 0; i < sl->size - 1; i++)
	{
		for (int j = 0; j < sl->size - 1 - i; j++)
		{
			if (sl->array[j] > sl->array[j + 1])
			{
				Swap(sl->array + j, sl->array + j + 1);
			}
		}
	}
}

//二分查找
int SeqListBanarySearch(SeqList *sl, SLDataType data)
{
	SLDataType left = 0;
	SLDataType right = sl->size - 1;
	SLDataType mid = 0;
	while (left <= right)
	{
		mid = left + ((right - left) >> 1);
		if (sl->array[mid] < data)
		{
			left = mid + 1;
		}
		else if (sl->array[mid] > data)
		{
			right = mid - 1;
		}
		if (sl->array[mid] == data)
		{
			return mid;
		}
	}
	return -1;
}

//(数据)删除同一个数全部
void SeqListRemoveAll(SeqList *sl, SLDataType data)
{
	int j = 0;
	for (int i = 0; i < sl->size; i++)
	{
		if (sl->array[i] != data)
		{
			sl->array[j++] = sl->array[i];
		}
	}
	sl->size = j;
	return;
}

//打印
void SeqListPrint(SeqList *sl)
{ 
	assert(sl);
	for (int i = 0; i < sl->size; i++)
	{
		printf("%d ",sl->array[i]);
	}
	printf("\n");
	return;
}

//检测是否需要扩容
void CheckCapacity(SeqList *sl)
{
	assert(sl);
	if (sl->size < sl->capacity)
	{
		return;
	}
	SLDataType newCapacity = sl->capacity * 2;
	SLDataType *newarray = (SLDataType*)malloc(sizeof(SLDataType) * newCapacity);
	if (newarray == NULL)
	{
		printf("内存开辟失败\n");
		return;
	}
	for (int i = 0; i < sl->size; i++)
	{
		newarray[i] = sl->array[i];
	}
	free(sl->array);
	sl->array = newarray;
	sl->capacity = newCapacity;
	newarray = NULL;
	return;
}

这里是我使用的一些测试用例,可参考
main.c:

#include "SeqList.h"

void Test()
{
	unsigned int c = 10;
	SeqListInit(&seqlist,c);
	SeqListPushFront(&seqlist, 5);
	SeqListPushFront(&seqlist, 0);
	SeqListPushFront(&seqlist, 1);
	SeqListPushFront(&seqlist, 2);
	SeqListPushFront(&seqlist, 5);
	SeqListPushFront(&seqlist, 3);
	SeqListPushFront(&seqlist, 4);
	SeqListPushFront(&seqlist, 5);
	SeqListPrint(&seqlist);

	SeqListRemoveAll(&seqlist,5);
	SeqListPrint(&seqlist);


#if 0
	SeqListBubbleSort(&seqlist);
	SeqListPrint(&seqlist);

	printf("%d\n",SeqListBanarySearch(&seqlist,3));
	printf("%d\n",SeqListBanarySearch(&seqlist,5));
#endif
#if 0
	for (int i = 0; i < 100; i++)
	{
		SeqListPushBack(&seqlist, i);
	}
	SeqListPrint(&seqlist);

	SeqListModify(&seqlist, 0,100);
	SeqListModify(&seqlist, 50,200);
	SeqListModify(&seqlist, 99,1000);
	SeqListPrint(&seqlist);
#endif
#if 0
	SeqListRemove(&seqlist, 50);
	SeqListRemove(&seqlist, 0);
	SeqListRemove(&seqlist, 99);
	SeqListPrint(&seqlist);

#endif
#if 0
	SeqListErase(&seqlist,0);
	SeqListErase(&seqlist,98);
	SeqListErase(&seqlist,50);
	SeqListPrint(&seqlist);
#endif

#if 0
	SeqListInsert(&seqlist,0,100);
	SeqListInsert(&seqlist,50,1000);
	SeqListInsert(&seqlist,101,2000);
	SeqListPrint(&seqlist);
#endif
#if 0
	printf("%d\n",SeqListFind(&seqlist,5));
	printf("%d\n", SeqListFind(&seqlist, 0));
	printf("%d\n", SeqListFind(&seqlist, 99));
	// 1 2
#endif
	//SeqListPushFront(&seqlist,10);
	//SeqListPushFront(&seqlist,20);
	//SeqListPrint(&seqlist);
	10 20 1 2

	//SeqListPopFront(&seqlist);
	//SeqListPopFront(&seqlist);
	//SeqListPrint(&seqlist);
	 1 2

	//SeqListPopBack(&seqlist);
	//SeqListPrint(&seqlist);
	 1

	return;
}

int main()
{
	Test();
	SeqListDestroy(&seqlist);
	return 0;

如果发现问题,可评论,我会及时改正…

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值