数据结构——顺序表

顺序表的简单介绍

顺序表的概念:顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。

特点:顺序表的特点是可以通过元素的索引快速访问数据,其访问时间复杂度为O(1)。

分类:它分为静态顺序表和动态顺序表

静态顺序表:使用定长数组存储元素。

劣势:它若是开少了不够用,开多又浪费,是不是很不方便。

因此,这里主要引用了动态的顺序表。(其实这个跟之前写过的通讯录非常像,同时那里静态的通讯录也写过了,这里就不过多介绍了)

至此,正文开始:

我们先去创建三个文件:分别为SeqList.c,SeqList.h,test.c 这里的取名方式都是按照它的英文缩写形式,也是为了好区分,未来回顾时更好看懂。

一:首先我们在头文件边写上一个结构体(为了可以多个文件都可以使用)

这是静态形式的:

//typedef int SLDataType;    这里为啥要自定义呢?也是为了后面更加方便的看出来这个结构类型
                              到了后面就能充分体会到这句话了
//#define N 100000
//struct SeqList
//{
//	SLDataType a[N];
//	int size;
//};

动态形式的:

cs 复制代码
typedef int SLDataType;
#define INIT_CAPACITY 4

// 动态顺序表 -- 按需申请
typedef struct SeqList
{
	SLDataType* a;
	int size;     // 有效数据个数
	int capacity; // 空间容量
}SL;

其实眨眼一看,这跟通讯录无不一样,对吧。

二:初始化

我们已经创建了结构体变量了,对此我们是不是得先把它初始化?

cs 复制代码
void SLInit(SL* ps)
{
	assert(ps);

	ps->a = (SLDataType*)malloc(sizeof(SLDataType)* INIT_CAPACITY);
                       这里就使用到了上面说的自定义,是不是可以更加清晰的看懂它扩容的对象?
	if (ps->a == NULL)
	{
		perror("malloc fail");
		return;
	}

	ps->size = 0;
	ps->capacity = INIT_CAPACITY;
}

注意的是:我们一般扩容时,扩它原来的两倍或四倍。

这里可能有人会问:为什么不用柔性数组?

答案:柔性数组是让这个数组的其他成员跟这个数组在同一块空间上面,不符合顺序表

三:销毁的函数

上面扩容了,使用完后是不是得释放,所以创建销毁的函数。

cs 复制代码
void SLDestroy(SL* ps)
{
	assert(ps);

	free(ps->a);
	ps->a = NULL;
	ps->capacity = ps->size = 0;   
}

四:打印

我们先写完基本的需求。我们得打印出来,验证正确性。

cs 复制代码
void SLPrint(SL* ps)
{
	assert(ps);

	for (int i = 0; i < ps->size; ++i)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

由于后面都用到检查,增加容量部分,我们先统一把它弄成一个函数

cs 复制代码
void SLCheckCapacity(SL* ps)
{
	assert(ps);
这里以每次增加2个空间容量

	if (ps->size == ps->capacity)
	{
	  SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * ps->capacity * 2);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}

		ps->a = tmp;
		ps->capacity *= 2;
	}
}

五:尾插

cs 复制代码
void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);
    //检查是否满了
    if (ps->size == ps->capacity)
	{
      SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * ps->capacity * 2);
	  if (tmp == NULL)
	  {
			perror("realloc fail");
			return;
      }

	  ps->a = tmp;
	  ps->capacity *= 2;
	} 
      ps->a[ps->size] = x;
	  ps->size++;
   当然也可以写成这样,都是一样的
      //ps->a[ps->size++] = x;

}

现在我们来分析它:

假如就是上面那个要尾插。

有效数据个数int size

首先我们知道6这个位置的下标 是不是size-1;那下一个是不是下标是size。想要尾插,我们已经开辟了一个空间了。所以直接去寻找它的下标,就可以了。

即:ps->a[ps->size] = x;

ps->size++;

六:尾删

cs 复制代码
void SLPopBack(SL* ps)
{
	assert(ps);

	// 暴力检查
	//assert(ps->size > 0);

	// 温柔的检查
	//if (ps->size == 0)
	     //return;

	ps->a[ps->size - 1] = 0;
	ps->size--;

}

这里有两种检查空指针的方法:暴力,温柔都可以

这里的尾删的原理也是很简单:

就是把最后的下标size-1的数赋值成0,然后再减1,即把原来倒数第二的位置,变成新的倒数第一位置,就完成了尾删的过程了。

七:头插

cs 复制代码
void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);

	SLCheckCapacity(ps);

	int end = ps->size - 1;
	while (end >= 0)
	{
		ps->a[end + 1] = ps->a[end];
		--end;
	}

	ps->a[0] = x;
	ps->size++;

	

原理:就是开辟后,把整体的挪右边。

八:头删

cs 复制代码
void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size > 0);

	int begin = 1;
	while (begin < ps->size)
	{
	   ps->a[begin - 1] = ps->a[begin];
		++begin;
	}

	ps->size--;

	
}

原理:差不多是覆盖法,就是将原本下标为1的数赋值给下标为0的数,然后下标2的数赋值给下标1,依次赋值,最终就完成了覆盖。

九:其中位置插入

cs 复制代码
void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);

	SLCheckCapacity(ps);

	int end = ps->size - 1;
	while (end >= pos)
	{
		ps->a[end + 1] = ps->a[end];
		--end;
	}

	ps->a[pos] = x;
	ps->size++;
}

原理:找到你要插入的数字前一个位置后,将其后面的数字整体后移。依次移

十:指定某一位置删

cs 复制代码
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);

	int begin = pos + 1;
	while (begin < ps->size)
	{
		ps->a[begin - 1] = ps->a[begin];
		++begin;
	}

	ps->size--;
}

原理:如图,将begin后面的数,整体向左移一位,来覆盖住begin这个数,来达到删除目的

十一:查找部分

cs 复制代码
int SLFind(SL* ps, SLDataType x)
{
	assert(ps);
	for(int i = 0; i < ps->size; ++i)
	{
		if (ps->a[i] == x)     如果等于你要查找的数字
		{
			return i;
		}
	}

	return -1;
}

十二. 疑问和易错

至此,就完成了顺序表的代码了。下面给出写顺序表是容易出现的疑问和错误:

1.有人可能会问,为什么扩容,使用完后不用是否释放空间呢?

现在我们来分析分析:

扩容:原地扩容,异地扩容

释放空间,是把使用权给操作系统

因此,尽量只扩不缩

好处:避免当你要使用时,被别的变量占用,那么我只能异地扩容了

只有当你确定不用时才释放

相当于,你家有个车位,当你家车开走,不使用车位时,你会把你家车位给别人使用吗?那你家车回来时放哪呢?厕所也一样,当你不使用时,你会把你家厕所会释放成公共厕所吗?为什么,应该你也想得明白吧。

free发生错误时,有两中原因:

1.一个指针是个野指针。或者说,申请了一个空间,你从中间为啥开始释放,而不是说从返回起始位置开始释放

怎么判断:

我们通过它的指针,我们改变了它指向的内容

2.数组指向的空间上面可能有越界。
3.错误

无法解析的外部符号

就是有声明,无定义
4.pf->指向的内容

pf.a 指向它的地址
5.

形参是实参的拷贝,形参的改变,不影响实参

改的是地址的变量,所以实参那里也会变
6.

十三.好了,最后给上完整代码:

Seqlist.c

cs 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include "Seqlist.h"

void SLInit(SL* ps)
{
	assert(ps);
	ps->a=(SqDataType*)malloc(sizeof(SqDataType )* Iint_Capacity);
	if (ps->a == NULL)
	{
		perror("malloc fail");
		return ;
	}
	ps->sz= 0;
	ps->capacity = Iint_Capacity;
}

//ͷ
void SLDestory(SL* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->capacity = ps->sz = 0;
}

//ӡ
void SLPrint(SL* ps)
{
	assert(ps);
	for (int i = 0; i < ps->sz; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}
//Ƿ
void Check_Capacity(SL*ps)
{
	if (ps->sz == ps->capacity)
	{
		SqDataType* temp = (SqDataType*)realloc(ps->a, sizeof(SqDataType) * ps->capacity * 2);
		if (NULL == temp)
		{
			perror("realloc");
			return;
		}
		ps->a = temp;
		ps->capacity *= 2;

	}
}
//β~~
void SLPushBack(SL* ps,SqDataType x)
{
	assert(ps);
	//Ƿ
	Check_Capacity(ps);
	ps->a[ps->sz] = x;
	ps->sz++;
}
//ͷ
void SLPushFront(SL* ps, SqDataType x)
{
	assert(ps);
	Check_Capacity(ps);
	int end = ps->sz - 1;
	while (end >= 0)
	{
		ps->a[end + 1] = ps->a[end];
		end--;

	}
	ps->a[0] = x;
	ps->sz++;
}

//βɾ
void SLPopback(SL* ps)
{
	assert(ps);
	ps->a[ps->sz - 1] = 0;
	ps->sz--;
}

//ͷɾ
void SLPopFront(SL*ps)
{
	assert(ps);
	int frist = 1;
	while (frist < ps->sz)
	{
		ps->a[frist-1] = ps->a[frist];
		frist++;
		
	}
	ps->sz--;
}

//ڲ
void Insert(SL* ps, SqDataType pos, SqDataType x)
{
	assert(ps);
	Check_Capacity(ps);
	int end = ps->sz-1;
	while (end >= pos)
	{
		ps->a[end+1] = ps->a[end];
		end--;
	}
	ps->a[pos] = x;
	ps->sz++;
}

//ɾ
void SLErase(SL* ps, SqDataType Pos)
{
	assert(ps);
	int begin = Pos + 1;
	while (begin < ps->sz)
	{
		ps->a[begin-1] = ps->a[begin];
		begin++;
	}
	ps->sz--;

}

//
int SLFind(SL* ps,SqDataType x)
{
	assert(ps);
	for (int i = 0; i < ps->sz; i++)
	{
		if (ps->a[i] == x)
		{
			return i;
		}
	}

}

Seqlist.h

cs 复制代码
#pragma once
#include<assert.h>
#include<stdlib.h>
#include<stdio.h>
//typedef int SqDataType;
//#define x   100
̬˳
//struct SeqList
//{
//	SqDataType a[x];
//	int sz;
//};

//̬˳
typedef int SqDataType;
#define Iint_Capacity 2

typedef struct SqList
{
	SqDataType* a;
	int sz;
	int capacity;
}SL;

void SLInit(SL * ps);
void SLDestory(SL* ps);
void SLPrint(SL* ps);
void SLPushBack(SL* ps, SqDataType x);
void SLPushFront(SL* s, SqDataType x);
void SLPopback(SL* s);
void SLPopFront(SL* ps);
void Insert(SL* ps, SqDataType pos, SqDataType x);
void SLErase(SL* s, SqDataType Pos);
int  SLFind(SL* s, SqDataType x);

test.c

cs 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"Seqlist.h"
Test1()
{
	SL s;
	SLInit(&s);
	SLPushBack(&s, 1);
	SLPrint(&s);
	SLPushBack(&s, 2);
	SLPrint(&s);
	SLPushBack(&s, 4);
	SLPrint(&s);
	SLPushBack(&s, 5);
	SLPrint(&s);
	//SLPopFront(&s);

	//SLPrint(&s);
	Insert(&s, 1, 6);
	SLPrint(&s);
	SLErase(&s, 2);
	SLPrint(&s);
	int ret=SLFind(&s, 5);
	printf("%d", ret);

}
int main()
{
	Test1();
	return 0;
}

今次鸡汤(最重要!!!!):

每个黎明之前都必须经历一个漫长而令人窒息的夜晚;

在任何辉煌到来之前,一定有太多不愉快的时刻和被人瞧不起的日子;

每一次掌声之前,一定有太多的哀叹,有太多冷漠的目光;每座山的山顶上总有贝壳;

所以每次你要放弃的时候,别忘了对自己说:来吧,坚持住,少年们!

相关推荐
吃着火锅x唱着歌6 分钟前
PHP7内核剖析 学习笔记 第三章 数据类型
android·笔记·学习
木市门13 分钟前
【GAMES101笔记速查——Lecture 22 Animation Cont】
图像处理·笔记·图形渲染
martian66513 分钟前
深入详解人工智能机器学习常见算法中的K-means聚类
人工智能·算法·机器学习·k-means聚类
、南城不南 ╯35 分钟前
最小二乘法拟合出二阶响应面近似模型
算法·最小二乘法
字节高级特工1 小时前
C++---入门
开发语言·c++·算法
信徒_1 小时前
常用排序算法
数据结构·算法·排序算法
幼安2291 小时前
交换瓶子(图论 贪心)
算法·图论
black_blank2 小时前
红与黑,,
算法
A黄俊辉A3 小时前
electron学习笔记(一)
笔记·学习·electron