数据结构·顺序表

目录

线性表

概念及结构

顺序表

静态顺序表

动态顺序表

接口实现 

SeqList.h

SeqList.c

 Test.c

结束语


线性表

线性表( linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

概念及结构

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

连续存储顺序,是不能跳跃的

顺序表

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

静态顺序表

使用定长数组存储元素

        静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N 定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。

静态顺序表价值较低 -- 不考虑 

动态顺序表

 扩容方式 -- 使用 realloc 库函数 -- 有两种扩容方式 原地扩容 与 异地扩容 后者代价大

扩容选择 -- 两倍

扩容两倍的原因

一次扩容多了,存在空间的浪费。一次扩容少了,频繁扩容,效率损失

接口实现 

SeqList.h

#pragma once	//方式二次引用
#define _CRT_SECURE_NO_DEPRECATE	//VS里面的检查 -- 其他编辑器不需要
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

//或者
//#ifndef __SEQLIST_H__
//#define __SEQLIST_H__
//……
//#endif 

//静态的顺序表	 -- 缺陷:当不知道要存储多少数据的时候就体现出来了
//#define N 10
//typedef int SLDataType;
//struct Seqlist
//{
//	SLDataType a[N];
//	int size;	//存储数据的个数
//};

//动态顺序表	-- 有两种扩容方式 原地扩容与异地扩容 后者代价大

//重命名类型 -- 方便后续改变存储类型
typedef int SLDataType;

typedef struct Seqlist
{
	SLDataType* a;
	int size;	//存储数据的个数
	int capacity;//存储空间的大小
}SL;

//初始化 -- 这里要接收地址,用指针接收,利用地址来与外部产生联系
void SLInit(SL* psl);

//销毁 -- 
void SLDestory(SL* psl);

//顺序表的打印 -- 方便检查
void SLPrint(SL* psl);

//头插头删 尾插尾删
//尾插
void SLPushBack(SL* psl, SLDataType x);

//头插
void SLPushFront(SL* psl, SLDataType x);

//尾删
void SLPoPBack(SL* psl);

//头删
void SLPoPhFront(SL* psl);

// 顺序表查找 -- 没找到返回-1
int SLFind(SL* psl, SLDataType x);

//顺序表在pos位置插入x
void SLInsert(SL* psl, size_t pos, SLDataType x);

// 顺序表删除pos位置的值
void SLErase(SL* psl, size_t pos);

//修改
void SLModify(SL* psl, size_t pos, SLDataType x);

SeqList.c

#include "SeqList.h"


//初始化
void SLInit(SL* psl)
{
	assert(psl);	//自身断言不可以为空 不可以传NULL过来
	psl->a = NULL;
	psl->size = psl->capacity = 0;
}

//销毁
void SLDestory(SL* psl)
{
	assert(psl);
	//if (psl->a)	//free自己会检查是否为NULL -- free NULL 没意义
	//{
		free(psl->a); 
		psl->a = NULL;
		psl->size = psl->capacity = 0;
	//}
}


//检查并扩容
void SLCheckCapacity(SL* psl)
{
	//检查容量
	if (psl->size == psl->capacity)
	{
		int newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;	//扩容两倍 -- 当为0的时候赋值4字节
		SLDataType* tmp = (SLDataType*)realloc(psl->a, newCapacity * sizeof(SLDataType));	//三种情况,原地、异地与失败 -- 为防止失败应使用临时指针
		if (tmp == NULL)					// 这里开辟空间大小容易出错,必须要记得后面的* sizeof(SLDataType) 不然开辟的空间不够
		{
			perror("realloc fail");
			return;
			//exit(-1); //或者这样直接返回,更直接
		}
		psl->a = tmp;
		psl->capacity = newCapacity;
	}
}

//打印
void SLPrint(SL* psl)
{
	assert(psl);
	for (int i = 0; i < psl->size; i++)
	{
		printf("%d ", psl->a[i]);
	}
	printf("\n");
}

//尾插
void SLPushBack(SL* psl, SLDataType x)
{	

	//assert(psl);
	//检查并扩容
	//SLCheckCapacity(psl);

	//psl->a[psl->size] = x;
	//psl->size++;
	
	//当SLInsert写好后就不需要这些了,直接调用
	SLInsert(psl, psl->size, x);
}

//头插
void SLPushFront(SL* psl, SLDataType x)
{
	//assert(psl);
	//SLCheckCapacity(psl);

	//挪动数据
	//int end = psl->size - 1;
	//while (end >= 0)
	//{
	//	psl->a[end+1] = psl->a[end];
	//	--end;
	//}
	//psl->a[0] = x;
	//psl->size++;

	//这里也不需要了,直接调用
	SLInsert(psl, 0, x); // -- 这就相当与头插了

}

//尾删
void SLPoPBack(SL* psl)
{
	//assert(psl);

	//比较温柔的检查
	//if (psl->size == 0)
	//{
	//	return;
	//}

	//暴力的检查		-- 会直接报错  在C++中更常用
	//assert(psl->size > 0);

	//psl->size--;		//realloc是不可以释放一部分的空间的所以不要free
	//					//也不需要清除掉数据,但是没必要,可以直接覆盖
	//					//这种的情况是由size为基准,由它访问
	//					//但是有bug:删掉的多了就会出现数据的越界

	//写完之后就可以替换掉之前的了

	SLErase(psl, psl->size-1);

}

//头删
void SLPoPhFront(SL* psl)
{
	//assert(psl);
	//assert(psl->size > 0);

	//一
	//int begin = 0;
	//while (begin < psl->size - 1)
	//{
	//	psl->a[begin] = psl->a[begin + 1];
	//	++begin;
	//}
	//--psl->size;

	//写完之后就可以替换掉之前的了
	SLErase(psl, 0);

}
//缩容:以时间换空间 -- 不常用
//不缩容:以空间换时间 -- 现在的设备比较空间都比较强
//realloc 缩容可能会异地缩容


//顺序表查找 -- 没找到返回-1
//这里直接遍历数组就可以了,在数据少的情况下这样反而更快 O(N) 级别
int SLFind(SL* psl, SLDataType x)
{
	assert(psl);

	for (int i = 0; i < psl->size; ++i)
	{
		if (psl->a[i] == x)
		{
			return i;
		}
	}
	return -1;
}

//顺序表在pos位置插入x -- 在前面插入
void SLInsert(SL* psl, size_t pos, SLDataType x)	//size_t 更符合库函数的设计
{
	assert(psl);
	assert(pos <= psl->size);

	SLCheckCapacity(psl);

	//挪动数据 法一: 用这种方法要注意算术转化,容易死循环
	//int end = psl->size - 1;
	//while (end>=(int)pos)
	//{	
	//	psl->a[end] = psl->a[end - 1];
	//	--end;
	//}

	//挪动数据 法二:这种方法更好,不需要强制转化
	size_t end = psl->size;
	while (end > pos)
	{
		psl->a[end] = psl->a[end - 1];
		end--;
	}

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


// 顺序表删除pos位置的值
void SLErase(SL* psl, size_t pos)
{
	assert(psl);
	assert(pos < psl->size);

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

	--psl->size;

}

//修改
void SLModify(SL* psl, size_t pos, SLDataType x)
{
	assert(psl);
	assert(pos < psl->size);

	psl->a[pos] = x;

}

 Test.c

#include "SeqList.h"

//尾插、头插、销毁测试
void TestSeqList1()
{
	SL s;
	//初始化 
	SLInit(&s);
	//尾插
	SLPushBack(&s, 1);
	SLPushBack(&s, 2);
	SLPushBack(&s, 3);
	SLPushBack(&s, 4);
	SLPushBack(&s, 5);
	SLPushBack(&s, 6);
	//打印观察
	SLPrint(&s);
	
	//头插
	SLPushFront(&s, 10);
	SLPushFront(&s, 20);
	SLPushFront(&s, 30);
	//打印观察
	SLPrint(&s);

	//销毁
	SLDestory(&s);

}


//尾插、尾删、删多了测试
TestSeqList2()
{
	//SLInit(NULL);	//测试传空指针 -- 到assert会直接报错
	SL s;
	SLInit(&s);
	SLPushBack(&s, 1);
	SLPushBack(&s, 2);
	SLPushBack(&s, 3);
	SLPushBack(&s, 4);
	SLPrint(&s);

	//尾删
	SLPoPBack(&s);
	SLPoPBack(&s);
	SLPrint(&s);
	//再尾删
	SLPoPBack(&s);
	SLPoPBack(&s);
	SLPrint(&s);

	//头删
	SLPushBack(&s,10);
	SLPushBack(&s,20);
	SLPushBack(&s,30);
	SLPushBack(&s,40);
	SLPrint(&s);

	SLDestory(&s);
}

//插入测试
TestSeqList3()
{
	SL s;
	SLInit(&s);
	SLInsert(&s, 0, 30);
	SLPushBack(&s, 40);
	SLPrint(&s);
}

//尾插、删除测试
TestSeqList4()
{
	SL s;
	SLInit(&s);
	SLPushBack(&s, 1);
	SLPushBack(&s, 2);
	SLPushBack(&s, 3);
	SLPushBack(&s, 4);
	SLPushBack(&s, 5);
	SLPrint(&s);

	SLErase(&s, 0);
	SLPrint(&s);
	int x = 0;
	
	scanf("%d", &x);
	int pos = SLFind(&s, x);
	if (pos != -1)
	{
		SLErase(&s, pos);
	}
	SLPrint(&s);

}

void memu()
{
	printf("****************************\n");
	printf("1、尾插数据 2、头插数据\n");
	printf("3、位置插入 7、打印数据 \n");
	printf("      -1、退出  \n");
	printf("****************************\n");

}

int main()
{
	//TestSeqList1();	//测试的时候尽量单独用一个函数测试
	//TestSeqList3();
	//TestSeqList4();

	SL s;
	SLInit(&s);
	int option = 0;
	int x = 0;
	int pos = 0;
	do
	{
		memu();
		printf("请输入你的操作:>");
		scanf("%d", &option);
		switch (option)
		{
			case 1:
				printf("请连续输入你想要插入的数据,以-1结束\n");
				scanf("%d", &x);
				while (x != -1)
				{
					SLPushBack(&s,x);
					scanf("%d", &x);
				}
				break;
			case 2:
				printf("请连续输入你想要插入的数据,以-1结束\n");
				scanf("%d", &x);
				while (x != -1)
				{
					SLPushFront(&s, x);
					scanf("%d", &x);
				}
				break;
			case 3:
				printf("请输入你想要插入的数据\n");
				scanf("%d", &x);
				printf("请输入你想要插入的位置\n");
				scanf("%d", &pos);
				SLInsert(&s, pos, x);
				break;
			case 7:
				SLPrint(&s);
				break;
		default:
			break;
		}

	} while (option != -1);

	SLDestory(&s);

	return 0;
}

菜单不完善了,很简单

我写这篇的时候发现这里不允许使用 //// 来二层注释 

结束语

别有幽愁暗恨生,此时无声胜有声。
                                        唐·白居易 《琵琶行》

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清风玉骨

爱了!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值