[数据结构]你已经是个大人了,快来写顺序表吧

这是一个目录

  • 线性表
  • 顺序表
    • 目标
    • 一点简单的准备工作
      • 初始化
      • 销毁
      • 打印
      • 增容函数
    • 接口函数
      • 尾插
      • 头插
      • 尾删
      • 头删
      • 指定位置增删函数
        • 查找指定位置
        • 在指定位置插入数据
        • 在指定位置删除数据
        • 头尾函数的船新写法
    • 最后

[数据结构]你已经是个大人了,快来写顺序表吧_第1张图片

又到了 编故事 啊不讲故事的时间,还是那个菜鸡大学生。在高数和c语言的毒打之下,终于挺过了大一的上半学期。精通打退堂鼓这一门乐器的他知道,如果不继续往下学习。很可能他当程序员的理想就要夭折,遂,淦顺序表。

线性表

线性表是啥?

线性表(linear list)是n个具有 相同特性 的数据元素的有限序列。

常见的线性表有哪些?
顺序表、链表、栈、队列、字符串… 它们在之后的文章都会提到,今天我们主要讲顺序表。

注:线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

顺序表

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
大概是这样:
[数据结构]你已经是个大人了,快来写顺序表吧_第2张图片
简单来说就是一个高配的数组。
[数据结构]你已经是个大人了,快来写顺序表吧_第3张图片

目标

  • 一个动态的顺序表
  • 实现基本的增删查改等接口函数

开工!

一点简单的准备工作

顺序表的本体(动态版本)

typedef int SLDataName;

typedef struct SeqList
{
	SLDataName* arr;  //指向动态开辟的数组
	size_t size;      //有效数据个数
	size_t capacity;  //容量
}SeqList;

初始化

int main()
{
	SeqList s;
	SeqListInit(&s);
}
  • 初始化可以防止顺序表里面的值不是奇奇怪怪的随机值,方便以后使用。
  • 这里取地址的原因:
    1. 我们一般推荐传址,万一这个结构体特别大,一拷贝压个栈,大家都别想活着。◇ヘ(;´Д`ヘ)纪念这里被夹的图片酱。◆
    2. 第二我们都知道函数里面的形参只是一份拷贝,形参改变是不影响实参的。
//顺序表初始化
void SeqListInit(SeqList* psl)
{
	psl->arr = (SeqList*)calloc(1, sizeof(SLDataName));
	psl->capacity = 1;
	psl->size = 0; 
}

这里的capacity也可以写成0,但是我为了方便接下来的扩容就没有这么写。

销毁

//顺序表销毁
void SeqListDestory(SeqList* psl)
{
	free(psl->arr);
	psl->arr = NULL;
	psl->capacity = 0;
	psl->size = 0;
}

不销毁的话会存在内存泄漏的问题。(虽然程序结束操作系统会帮你自动回收,但是我们不能养成这种坏习惯)

free的时候编译器才会检查指针是否越界。这时候报的错一般都是越界的问题。

销毁的函数很好写,全部都清空就完事了。

打印

//顺序表打印
void SeqListPrint(SeqList* psl)
{
	for (int i = 0; i < psl->size; i++)
	{
		printf("%d ", psl->arr[i]);
	}
	printf("\n");
}

遍历就好,轻松愉快。

增容函数

//检查空间,如果满了,进行增容

void CheckCapacity(SeqList* psl)
{
    assert(psl); //断言防止指针为空
	
	if (psl->size == psl->capacity)
	{
		SLDataName* tmp =(SLDataName*) realloc(psl->arr,sizeof(SLDataName*) *(psl->capacity) * 2);
		if (tmp != NULL)
		{
			psl->arr = tmp;
			(psl->capacity) *= 2;
			printf("Success!\n");
		}
		else
		{
			printf("Opps!\n");  //扩容失败
			exit(-1)  //结束程序
		}
	}
}

如果有效数据等于容量我们就可以考虑增容了。
这里是选择扩容到原来的两倍,当然你想扩多大就扩多大,数据结构在这个方面还是很宽松的。

  • 使用realloc的时候注意要先用一个tmp数组接收,防止扩容失败导致数据丢失。(具体可以去msdn上面查找realloc函数的使用方法)
  • 如果扩容失败导致tmp指向为NULL就结束程序。

接口函数

尾插

//顺序表尾插
void SeqListPushback(SeqList* psl, SLDataName x)
{
	assert(psl);
	
	CheckCapacity(psl);
	psl->arr[psl->size] = x;
	psl->size++;
}

尾插还是很简单的,arr[psl->size]正好是我们需要插入数字的位置,最后记得有效数据个数要加一就行。
[数据结构]你已经是个大人了,快来写顺序表吧_第4张图片

头插

//顺序表头插
void SeqListPushhead(SeqList* psl, SLDataName x)
{
	assert(psl);
	
	CheckCapacity(psl);
	for (int i = psl->size; i > 0; i--)
	{
		psl->arr[i] = psl->arr[i - 1];
	}
	psl->size += 1;
	psl->arr[0] = x;
}

只要将数据依次往后挪一位,最后在第一位插入数据,就完成了数据的头插。
[数据结构]你已经是个大人了,快来写顺序表吧_第5张图片

尾删

//顺序表尾删
void SeqListPopback(SeqList* psl)
{
	assert(psl);
	
	if (psl->size == 0)
	{
		printf("顺序表为空\n");
		return;
	}
	psl->size --;
}

有效数据减少就是删了。
什么,你说没删,你看的到吗?看不到,看不到就是删了。

反正增加数据的时候一覆盖死无对证。
注意顺序表为空的情况即可。

头删

//顺序表头删
void SeqListPophead(SeqList* psl)
{
	assert(psl);
	
	if (psl->size == 0)
	{
		printf("顺序表为空\n");
		return;
	}
    for (int i = 0; i <psl->size-1; i++)
	{
		psl->arr[i] = psl->arr[i + 1];
	}
	psl->size --;
}

[数据结构]你已经是个大人了,快来写顺序表吧_第6张图片
将头插的思路倒过来加上尾删减去有效数据的操作就是头删这个缝合怪。
依旧注意顺序表不能为空。

指定位置增删函数

查找指定位置

//顺序表查找
int SeqListFind(SeqList* psl, SLDataName x)
{
	for (int i = 0; i < psl->size; i++)
	{
		if (psl->arr[i] == x)
			return i;
	}
	return -1;
}

遍历,找到返回下标,没找到返回-1。
简单粗暴,甚得人心。

在指定位置插入数据

//顺序表在pos位置插入x
void SeqListInsert(SeqList* psl, size_t pos, SLDataName x)
{
	// 暴力检查
	assert(psl);

	// 温和检查
	if (pos > psl->size)
	{
		printf("pos 越界:%d\n", pos);
		return;
	}

	CheckCapacity(psl);
	for (int i = psl->size; i > pos; i--)
	{
		psl->arr[i] = psl->arr[i - 1];
	}
	psl->size ++;
	psl->arr[pos] = x;
}

就是头插写法的推广版本,不多赘述。

还有一种写法:(i >= (int)pos)循环操作写psl->arr[i+1] = psl->arr[i ];
注:由于pos的类型是size_t,要注意潜在的整形提升问题。

在指定位置删除数据

//顺序表删除pos位置的值
void SeqListErase(SeqList* psl, size_t pos)
{
	assert(psl);
	assert(pos < psl->size);
	
	if (psl->size == 0)
	{
		printf("顺序表为空\n");
    	return;
    }
	for(int i = pos; i < psl->size-1; i++)
	{
		psl->arr[i] = psl->arr[i + 1];
	}
	psl->size--;
}

覆盖就好。

头尾函数的船新写法

有了指定位置增删函数,我们就可以把它用到特殊的情况。

  • 尾插就是在size位置插入数据
  • 尾删就是在size-1位置删除数据
  • 头插就是在0位置插入数据
  • 头删就是在0位置删除数据

可以节约一大票时间,快速写好顺序表不是梦。

void SeqListPushBack(SeqList* psl, SLDataName x)
{
	assert(psl);
	
	SeqListInsert(psl, psl->size, x);
}

void SeqListPopBack(SeqList* psl)
{
	assert(psl);

	SeqListErase(psl, psl->size-1);
}

void SeqListPushFront(SeqList* psl, SLDataType x)
{
	assert(psl);

	SeqListInsert(psl, 0, x);
}

void SeqListPopFront(SeqList* psl)
{

	assert(psl);

	SeqListErase(psl, 0);
}

最后

  • 顺序表直接访问某个元素的时间复杂度为O(1)
  • 头插,头删或者指定位置插入的时间复杂度为O(n)

多画画图这些基本上就出来了。(菜鸡大学生如是说)

你可能感兴趣的:(C语言入土之路,数据结构,c语言)