线性表-顺序表

线性表

一.顺序表

关于顺序表的时间复杂度不过多于赘述,因为都为O(n)。

顺序表的优缺点

优点

  • 无需为表示结点间的逻辑关系而增加额外的存储空间
  • 可方便地随机存取表中的任一元素

缺点

  • 插入或删除需要移动大量的数据元素
  • 当线性表长度变化较大时难以确定存储空间的容量,造成存储空间的浪费

如何创建一个顺序表

首先我们需要知道,顺序存储结构有三个重要的属性:

  • 存储数据元素的空间
  • 线性表的最大容量:MAXSIZE
  • 线性表当前长度:last

代码如下

#define MAXSIZE 100
struct Linear_list{
	datatype elem[MAXSIZE];//定义数组域
	int last;//存放数组最后一个元素的下标
}Seqlist;

注意

“数组的长度”和“线性表的长度”是两个概念,数组的长度用来存放线性表的存储空间,数组长度是不变的,而线性表的长度则会随着插入和删除操作发生变化,注意:任何时候线性表的长度都小于等于数组长度

与顺序表有关的算法

01.插入算法

首先我们要知道插入算法的步骤:

  • 确定插入元素的位置i
  • 将a[i]~a[last]顺序向下移动,为新元素让出空间
  • 插入新元素

注意:

  • 我们需要先检查顺序表的表长是否满了,表满的情况下不能执行插入操作
  • 要检验插入位置的有效性,有效范围为i <= 1 <= last+1
  • 注意是从左往右添加还是从右往左添加元素
  • 记得修改last,也就是修改下标的最后一位,使其仍指向最后一个元素

代码

插入部分算法如下:

void insert_list(struct seqlist *L, int x ,int area)
{
	//判断表是否为空
	if (L->last == MAXSIZE - 1)
	{
		printf("表满,无法插入");
		return 0;
	}
	//判断插入位置是否合法
	if (area < 1 || area > L->last + 2)
	{
		printf("插入位置无效");
		return 0;
	}
	//执行插入运算
	for (int i = L->last-1; i > area; i--)
		L->elem[i + 1] = L->elem[i];//移位运算
	L->elem[area+1] = x;
	L->last++;
	return 1;
}

注意:
struct seqlist *L 是结构体指针,x是插入的元素,area是插入元素的位置,之所以使用结构体指针,是因为结构体指针会直接指向结构体的首地址,所以此时我们是直接操作整个线性表,如果传入结构体成员或整个结构体的话则是操作了在函数insert_list里面系统新创建的线性表,而其可行域只在insert_list函数里面有效,这就会导致在主函数遍历线性表还是原先的线性表,所以要用结构体指针

完整代码如下:

#include<stdio.h>
#define MAXSIZE 50
typedef int datatype;
static int j;

struct seqlist {
	datatype elem[MAXSIZE];
	int last;
}Seqlist_list;

void insert_list(struct seqlist *L, int x ,int area)
{
	//判断表是否为空
	if (L->last == MAXSIZE - 1)
	{
		printf("表满,无法插入");
		return 0;
	}
	//判断插入位置是否合法
	if (area < 1 || area > L->last + 2)
	{
		printf("插入位置无效");
		return 0;
	}
	//执行插入运算
	for (int i = L->last-1; i > area; i--)
		L->elem[i + 1] = L->elem[i];//移位运算
	L->elem[area+1] = x;
	L->last++;
	return 1;
}

int main()
{
	int insert,
		i;

	printf("请输入顺序表中有多少元素: ");
	scanf_s("%d", &Seqlist_list.last);

	printf("请输入线性表的所有元素: ");
	for (i = 0; i < Seqlist_list.last; i++)
		scanf_s("%d", &Seqlist_list.elem[i]);

	printf("要插入的元素是:");
	scanf_s("%d", &insert);

	//查找算法查找插入位置
	for (j = Seqlist_list.last-1; j >= 0; j--)
	{
		if (insert > Seqlist_list.elem[j])
			break;
	}

	insert_list(&Seqlist_list, insert ,j);

	for (i = 0; i < Seqlist_list.last; i++)
	{
		printf("%d ", Seqlist_list.elem[i]);
	}

	return 0;
}

02.删除算法

同样我们要了解删除算法的步骤:

  • 将a[i]~a[last]顺序向上移动
  • 修改last使其指向最后一位

注意

  • 首先检查顺序表是否为空,空表无法进行删除操作
  • 要检验插入位置的有效性,有效范围为i <= 1 <= last+1
  • 删除元素后,该数据不存在,如果需要,可以先取出该元素,再做删除
  • 注意是从左向右还是从右向左执行删除操作

代码

删除算法部分代码如下:

void delete_list(struct seqlist* L, int area)
{
	if (area < 1 || area > L->last + 2)
	{
		printf("删除位置无效");
		return 0;
	}
	for (int i = area+1; i <= L->last - 1; i++)
		L->elem[i - 1] = L->elem[i];//数据上移一位
	L->last--;
	return 1;
}

注意事项与插入算法基本相同,可以返回去看。

完整代码:

#include<stdio.h>
#define MAXSIZE 50
typedef int datatype;
static int j;

struct seqlist {
	datatype elem[MAXSIZE];
	int last;
}Seqlist_list;

void delete_list(struct seqlist* L, int area)
{
	if (area < 1 || area > L->last + 2)
	{
		printf("删除位置无效");
		return 0;
	}
	for (int i = area+1; i <= L->last - 1; i++)
		L->elem[i - 1] = L->elem[i];//数据上移一位
	L->last--;
	return 1;
}

int main()
{
	int delete ,i;

	printf("请输入顺序表中有多少元素: ");
	scanf_s("%d", &Seqlist_list.last);

	printf("请输入线性表的所有元素: ");
	for (i = 0; i < Seqlist_list.last; i++)
		scanf_s("%d", &Seqlist_list.elem[i]);

	printf("要删除的元素是:");
	scanf_s("%d", &delete);

	//查找算法查找删除位置
	for (j = Seqlist_list.last - 1; j >= 0; j--)
	{
		if (delete >= Seqlist_list.elem[j])
			break;
	}

	delete_list(&Seqlist_list,j);

	for (i = 0; i < Seqlist_list.last; i++)
	{
		printf("%d ", Seqlist_list.elem[i]);
	}

	return 0;

二.线性表的链式存储结构

01.单链表

单链表是通过一组任意的存储单元来存储线性表中的数据元素的。

单链表与顺序表的区别就是单链表除了存储数据外还需要存储后继元素的地址,所以,我们把存储元素信息的域称为数据域,存储直接后继位置的域称为指针域,由这两部分组成的叫做一个结点(如下图所示)
在这里插入图片描述

n个结点连起来就是一个链表,因为每个结点都只有一个指向后继的指针,所以叫做单链表。

头指针

第一个结点都没有前趋,所以我们需要一个头指针指向第一个结点,
最后一个结点没有后继,所以最后一个结点里的指针域为NULL,即空指针。

  • 但是不管链表是否为空表,头指针一定存在
  • 通常我们用头指针来标识一个链表,如果头指针为NULL,啧意味着这是一个空表
  • 如果有头结点的话会变成指向头结点的指针
  • 通常用头指针作为链表的名字

头结点

通常会在第一个结点之前放一个称为头结点的结点。他的数据域一般没有意义,可以存放链表的表长,注意他的指针域存放的是第一个结点的数据域的地址,而不是头指针,头指针指向了头结点的数据域,添加头结点是为了方便链表的操作(给第一个元素添加了前趋,增加和删除操作就和其他元素一样了,但是造成了存储空间的浪费,因为多申请了一个空间),空链表的话头结点的指针域也为NULL,
在这里插入图片描述

代码

#include<stdio.h>
typedef int elemtype;

struct node {
	elemtype data; //数据域
	struct node* next; //指针域
}LNode ,*LinkList;
//LNode是结点的类型,LinkList是指向LNode类型结点的指针类型,通常将表示一个链表的投资着呢说明为LinkList类型的变量

LinkList H; //声明一个头指针,注意这个在实际运行中并不是放在此处
//LinkList *H 这个写法是错误的,因为LinkList本身就是结构体指针类型,如果加*则H变成了双指针类型

02.相关算法

建立单链表

这里首先要说一下,结点在内存中的位置是怎么得到的,因为链表与顺序表不同,顺序表是一段连续的存储空间,其大小和长度由规定好的数组长度以及顺序表的表长决定,而链表是动态管理的存储结构,所以每一次添加结点我们都需要向内存申请一个结点的存储空间,申请的方式就是用malloc

H = (LinkList)malloc(sizeof(LNode));//申请一个LinkList(结构体指针)类型的结点的空间,大小为LNode类型的大小
//注意H本身也是指针变量(参考上一条代码),所以用的是LinkList类型,

//如果不用这个空间就可以用free释放掉
free(H);
(01)头插法

所谓头插法就是从链表的头部插入结点,假设有一个带有头结点的链表,要用头插法插入一个元素(假设),那就需要让头指针指向新元素的数据域,然后让新元素的指针域指向原先第一个元素的数据域,差不多这个就是头插法的过程,实际的看代码吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值