[数据结构](3)顺序表

文章目录

  • 线性表
  • 什么是顺序表
    • 静态顺序表
    • 动态顺序表
  • 接口实现
    • 头文件SeqList.h
    • 函数实现SeqList.c
      • 初始化
      • 销毁
      • 插入
      • 删除
      • 尾插 尾删 头插 头删
      • 打印
      • 查找
      • 完整代码
  • 测试test.c
  • 总结

线性表

线性表*(linear list)*是数据结构的一种,一个线性表是n个具有相同特性的数据元素的有限序列。常见的线性表:顺序表、链表、栈、队列、字符串…
我们说“线性”和“非线性”,只在逻辑层次上讨论,而不考虑存储层次,所以链表依旧是线性表。

下面我们先来讲讲顺序表。

什么是顺序表

顺序表是在内存中以数组的形式保存的线性表,是将表中的结点依次存放在内存中一组地址连续的存储单元中。

静态顺序表

#define N 100
typedef int SLDataType;

typedef struct SeqList
{
	SLDataType array[N]; //静态容量
	size_t size;         //有效数据个数
}SeqList;

动态顺序表

typedef int SLDataType;

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

实际很多场景不知道要存多少数据,有时静态顺序表不是开大了就是开小了,所以更多使用动态顺序表,下面来介绍动态顺序表的实现(有点类似之前的通讯录)

接口实现

头文件SeqList.h

以下是我们要实现的功能:

// SeqList.h
#pragma once
#include 
#include 
#include 

typedef int SLDataType;
typedef struct SeqList
{
	SLDataType* a;
	size_t size;
	size_t capacity;
}SeqList;

// 对数据的管理:增删查改 
void SeqListInit(SeqList* ps);//初始化
void SeqListDestory(SeqList* ps);//销毁
void SeqListPrint(SeqList* ps);//打印顺序表
void SeqListPushBack(SeqList* ps, SLDataType x);//尾插
void SeqListPushFront(SeqList* ps, SLDataType x);//头插
void SeqListPopFront(SeqList* ps);//前删
void SeqListPopBack(SeqList* ps);//尾删

int SeqListFind(SeqList* ps, SLDataType x);//查找
void SeqListInsert(SeqList* ps, size_t pos, SLDataType x);//顺序表在pos位置插入x
void SeqListErase(SeqList* ps, size_t pos);//删除pos位置的值

函数实现SeqList.c

初始化

void SeqListInit(SeqList* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = 0;
}

销毁

void SeqListDestory(SeqList* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->capacity = ps->size = 0;
}

注意到头插尾插都属于插入,头删尾删都属于删除,所以我们先写插入和删除。

插入之前得先检查容量

void SeqListCheckCapacity(SeqList* ps)
{
	assert(ps);
    //如果满了,那么扩容
	if (ps->size == ps->capacity)
	{
		size_t newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//2倍扩容,第一次扩容给4个容量
		SLDataType* tmp = realloc(ps->a, sizeof(SLDataType) * newCapacity);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		else
		{
			ps->a = tmp;
			ps->capacity = newCapacity;
		}
	}
}

realloc还不熟悉可以去复习一下[c语言]动态内存分配|malloc realloc calloc函数|相关错误|习题|柔性数组

exit(-1)可以直接结束程序,括号中的-1为程序退出的返回代码,表示错误退出。与return不同,return是结束函数。

插入

void SeqListInsert(SeqList* ps, size_t pos, SLDataType x)
{
	assert(ps);
	if (pos > ps->size)
	{
		printf("pos 越界:%d\n", pos);
		return;
	}
	SeqListCheckCapacity(ps);
    
	size_t end = ps->size;
	while (end > pos)
	{
		ps->a[end] = ps->a[end - 1];
		--end;
	}
	ps->a[pos] = x;
	ps->size++;
}

assert断言相较用if判断更暴力(为假则终止程序),这边判断给的pos是否越界不用这么暴力(为假则终止函数)

可能有人要问,传入的pos如果是-1怎么办?size_t是无符号整型,不用担心这个问题。

然后从后向前挪动数据,腾出空间, 这样就把值插入了。

删除

void SeqListErase(SeqList* ps, size_t pos)
{
	assert(ps);
	if (pos >= ps->size)
	{
		printf("pos 越界:%d\n", pos);
		return;
	}

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

因为数组的存储是连续的,所以删除只能是后面一个覆盖前面一个。

尾插 尾删 头插 头删

写完上面的,这几个就很简单了。

void SeqListPushBack(SeqList* ps, SLDataType x)
{
	assert(ps);
	SeqListInsert(ps, ps->size, x);
}
void SeqListPopBack(SeqList* ps)
{
	assert(ps);
	SeqListErase(ps, ps->size - 1);
}
void SeqListPushFront(SeqList* ps, SLDataType x)
{
	assert(ps);
	SeqListInsert(ps, 0, x);
}
void SeqListPopFront(SeqList* ps)
{
	assert(ps);
	SeqListErase(ps, 0);
}

打印

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

查找

找到就返回下标,未找到返回-1

int SeqListFind(SeqList* ps, SLDataType x)
{
	assert(ps);
	for (int i = 0; i < ps->size; ++i)
	{
		if (ps->a[i] == x)
		{
			return i;
		}
	}
	return -1;
}

完整代码

//SeqList.c
#include "SeqList.h"

void SeqListInit(SeqList* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = 0;
}

void SeqListDestory(SeqList* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->capacity = ps->size = 0;
}

void SeqListCheckCapacity(SeqList* ps)
{
	assert(ps);

	if (ps->size == ps->capacity)
	{
		size_t newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		SLDataType* tmp = realloc(ps->a, sizeof(SLDataType) * newCapacity);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		else
		{
			ps->a = tmp;
			ps->capacity = newCapacity;
		}
	}
}

void SeqListInsert(SeqList* ps, size_t pos, SLDataType x)
{
	assert(ps);
	if (pos > ps->size)
	{
		printf("pos 越界:%d\n", pos);
		return;
	}
	SeqListCheckCapacity(ps);

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

void SeqListErase(SeqList* ps, size_t pos)
{
	assert(ps);
	if (pos >= ps->size)
	{
		printf("pos 越界:%d\n", pos);
		return;
	}

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

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

void SeqListPopBack(SeqList* ps)
{
	assert(ps);
	SeqListErase(ps, ps->size - 1);
}

void SeqListPushFront(SeqList* ps, SLDataType x)
{
	assert(ps);
	SeqListInsert(ps, 0, x);
}

void SeqListPopFront(SeqList* ps)
{
	assert(ps);
	SeqListErase(ps, 0);
}

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

int SeqListFind(SeqList* ps, SLDataType x)
{
	assert(ps);
	for (int i = 0; i < ps->size; ++i)
	{
		if (ps->a[i] == x)
		{
			return i;
		}
	}
	return -1;
}

测试test.c

//test.c
#include "SeqList.h"

void menu()
{
	printf("***********************\n");
	printf("**** 1.头插 2.头删 ****\n");
	printf("**** 3.尾插 4.尾删 ****\n");
	printf("**** 5.插入 6.删除 ****\n");
	printf("**** 7.打印 8.查找 ****\n");
	printf("****    0.退出     ****\n");
	printf("***********************\n");
}

int main()
{
	SeqList s;
	SeqListInit(&s);
	int option = 0;
	SLDataType x;
	size_t pos;
	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &option);
		switch (option)
		{
		case 1:
			printf("输入要插入的值:");
			scanf("%d", &x);
			SeqListPushFront(&s, x);
			printf("插入成功\n");
			break;
		case 2:
			SeqListPopFront(&s);
			printf("删除成功\n");
			break;
		case 3:
			printf("输入要插入的值:");
			scanf("%d", &x);
			SeqListPushBack(&s, x);
			printf("插入成功\n");
			break;
		case 4:
			SeqListPopBack(&s);
			printf("删除成功\n");
			break;
		case 5:
			printf("输入要插入的位置:");
			scanf("%u", &pos);
			printf("输入要插入的值:");
			scanf("%d", &x);
			SeqListInsert(&s, pos, x);
			printf("插入成功\n");
			break;
		case 6:
			printf("输入要删除的位置:");
			scanf("%u", &pos);
			SeqListErase(&s, pos);
			printf("删除成功\n");
			break;
		case 7:
			SeqListPrint(&s);
			break;
		case 8:
			printf("输入要查找的值:");
			scanf("%d", &x);
			int index = SeqListFind(&s, x);
			if (index == -1)
			{
				printf("未找到\n");
			}
			else
			{
				printf("下标为:%d\n", index);
			}
			break;
		}

	} while (option);
	printf("退出程序\n");
	SeqListDestory(&s);
	return 0;
}

虽然我们在头文件定义了类型typedef int SLDataType;,但要修改数据类型不能光改这里,还有scanf("%d", &x);里的格式占位符也要跟着改,这算是c语言的一个缺陷吧。


总结

顺序表在内存中的存储是连续的

  • 顺序表的优点:支持随机访问,查找速度快,时间复杂度是 O ( 1 ) O(1) O(1)

  • 顺序表的缺点:空间利用率不高,删除插入效率低,时间复杂度为 O ( n ) O(n) O(n),因为要依次向前挪或者向后移。

链表优缺点几乎与它互补,我们下一篇数据结构博客再聊。

接下来还会更新一些数组相关的练习,还望有大佬指点,多多支持~

你可能感兴趣的:(数据结构,数据结构,算法,c语言,c++,后端)