目录
一、前情提要
作为一名计算机专业的学生,每次查资料的时候发现,很多的资料查阅需要付费而且答案冗长不精炼。于是自己就想着能不能自己写一个免费“合集”,细致的去介绍数据结构与算法。一方面巩固自己的知识。更重要的是,如果可以帮助很多初学者解决问题那么这就是一件有意义的事。这个合集是以Weiss教授的《数据结构与算法分析C语言描述》为蓝本,自己修改了一些章节的学习位置(为了帮助理解),总而言之希望大家看到这篇文章的时候能够解决很多问题。最后一点,由于笔者能力有限,如果出现了一些错误欢迎大家评论区来指正,一起做好这个专栏。
Q:为什么要用C语言去写?
A:大多数人大一学的就是C语言,这样受众更广。并且数据结构与算法重在逻辑,理解清楚是重点。
Q:掌握了知识点,不知道自己实现的是不是对的。
A:放心,每一篇文章都会传一份C语言的代码上去,标有详细注释。自己可以先实现一遍,如果不对,可以进行对照。
二、线性表的简介与分析
(一)抽象数据类型(ADT)
抽象数据类型:
(1) 也叫ADT,是一系列操作的集合。
(2)简单点来说就是一种自己设置的数据类型,和整型、浮点型、布尔类型一样。
(3)在整型、浮点型、布尔类型中有和其性质相关的操作,抽象数据类型也有和其性质相关的操作。例如:在超级玛丽中,马里奥就是一个抽象数据类型,他具有一系列操作跑、跳、发射子弹等。
(二)线性表
为啥一上来先介绍抽象数据类型呢?因为线性表就是一种抽象数据类型,在一些资料上也被写成“表ADT”。不仅仅有线性表,以后要接触的栈和队列都是一种抽象数据类型。
为啥说线性表是抽象数据类型呢?比如:我要写一张成绩表,成绩表就是抽象出来的对象,里面的成绩就是对象中的数据元素,像插入(Insert)和查询(Query)都是将二者建立关系的操作。
可能大家看到有教材上写顺序表,其实顺序表是线性表中一种(顺序)存储方式,是指利用一段连续的内存地址来储存线性表的数据元素(data)。在C语言当中,这常使用数组来实现。本篇也主要是写顺序表和其代码。与其相对的则是链表。下面会详细介绍。
总结:线性表包括顺序表和链表!!是一种抽象数据类型。
(三)线性表存储方式与实现
一些简单的术语说一下。
假设:我们要处理一张形如A1,A2,A3...,An的普通表。
1. 这个表的大小就是n(A1,A2...An都是元素,总共n个元素嘛)。
2. 在表中任意取俩元素,假设取Ai和A(i+1),那么则称Ai是A(i+1)的前驱,A(i+1)是Ai的 后继。
(1)表的简单实现
众所周知表有一系列操作,比如:输出表中数据(PrintList),清空表中数据(MakeEmpty),
查找数据(Find),插入元素(Insert),删除元素(Delete),返回某个位置上的元素(FindKth)。
这些操作我就不解释了哈,都是字面意思。
(2)表的存储方式
说完了简单操作,再来聊一聊存储方式,一种是 顺序存储,另外一种是 链式存储,二者各有优劣。
顺序存储:
在物理意义上相邻,比如ABCDEFG。有着严格的物理顺序。一般由数组实现。
链式存储:
在物理意义上不相邻,它可以用类似于链子一样链接不同元素,比如说ACDFGB。虽说物理上不相邻,但在逻辑上是相邻的。就是说最前面俩元素AC,在英文字母中不相邻,但在这个链式存储中属于前驱和后继的关系。一般由链表实现。
二者的优劣性如何?
顺序存储——顺序表:
第一、对于表的最大值进行估计,通常需要估计得大一些,然后开辟一段连续的空间进行储存,而这会浪费大量的空间,这是严重的局限,特别是存在许多未知大小的情况下。
第二、如果要删除某个中间位置的元素,则后面的元素就必须要整体往前挪一位。毕竟别人有着严格的物理顺序,一个地方断档了,后面的元素肯定要接上。这样花费的时间就会变多咯。
链式存储——链表:
为了避免插入和删除的开销,允许表可以不连续存储,所以删除元素的时候,并不需要后面的元素整体移位。因为他们本身就会不连续,就像一条条链子连起来,我们只需要把其中一个元素“解绑”,然后将“链子”重新链接下一个元素就行。
三、代码及其功能实现
(一)顺序表的简单数组实现
有两种写法,以后见到更多的可能是第二种。
#include <stdio.h>
#include <stdlib.h>
//表的简单数组实现
const int N=15;//可以略微比目标容量大一些
struct ArrayList
{
int Element[N];
int Length;//最大长度
int Size;//实际长度
}List;
void Init()
{
List.Length=N;
List.Size=0;
}
void PrintList()//输出表中数据
{
if(List.Size==0)
{
printf("空表,没有元素");
return;
}
for(int i=0;i<List.Size;i++)
{
printf("%d ",List.Element[i]);
}
printf("\n");
}
int Find(int x)//查询值为x的位置,并返回下标
{
if(List.Size==0)
{
printf("空表,无元素查询");
return -1;
}
for(int i=0;i<List.Size;i++) //遍历查找元素
{
if(List.Element[i]==x) return i;
}
printf("未查询到与之匹配的元素");//如果进行到这一步还找不到,那么就是不存在
return -1;
}
int FindKth(int k) //返回某个位置上的元素,位置从0开始
{
if(k>=0&&k<List.Size)
{
return List.Element[k];
}
else
{
printf("该位置上没有元素");
return -1;
}
}
void Insert(int k,int x)//在第k位插入
{
if(List.Size==List.Length)
{
printf("内存不足,请扩容");
return;
}
for(int i=List.Size;i>k;i--) //第k位后面的所有元素向后挪一位,给插入的元素腾出空间。
{ //元素拷贝到后一位。
List.Element[i+1]=List.Element[i]; //必须反着拷贝;
//如果正着拷贝那么Element[i+1]=Element[i],Element[i+1]原来的值就被覆盖掉了;
//后面Element[i+2]=Element[i+1]就没办法使用Element[i+1]的值去拷贝。
}
List.Element[k]=x;//第k个位置已经腾出空来了 ,直接赋值就好。
List.Size++;//实际长度+1。
}
void Delete(int k) //在第k位删除
{
if(List.Size==0)
{
printf("空表,无元素删除");
return;
}
//第k位后面的元素往前挪一位,覆盖掉就好了
//或者第k位前面的元素往后面挪一位,但这样感觉挺别扭的不推荐。
for(int i=k+1;i<List.Size;i++)
{
List.Element[i-1]=List.Element[i];
}
List.Size--;//实际长度-1。
}
void MakeEmpty()
{
List.Size=0;//将个数设置成0就行,原先的数据就变成了脏数据。
}
int main()
{
Init();//初始化
Insert(0,1);//插入1.2.3.4
Insert(1,2);
Insert(2,3);
Insert(3,4);
PrintList();//展示元素
printf("%d\n",Find(3));//查询值为3的下标,下标从0开始。
printf("%d\n",FindKth(2));//查询下标为2的下标。
Delete(2);
Delete(3);
PrintList();
MakeEmpty();
PrintList();
return 0;
}
#include <stdio.h>
#include <stdlib.h>
//表的简单数组实现
const int N=15;//表示表的容量最大值。
typedef struct ArrayList//数据集合
{
int* Element;
int Length;//数组最大长度
int Size;//数组实际长度
}MyArray;
MyArray Init()
{
MyArray Array; //创建结构体
Array.Element=(int*)malloc(N*sizeof(int));//申请内存
if(!Array.Element)
{
printf("初始化失败");
exit(0);
}
Array.Length=N; //最大长度为N
Array.Size=0; //实际长度初始化为0
return Array; //返回即可
}
void PrintList(MyArray *arr) //输出所有的数据
{
if(arr->Size==0)
{
printf("空表,没有元素");
return;
}
else
{
for(int i=0;i<arr->Size;i++) //遍历即可
{
printf("%d ",arr->Element[i]);
}
printf("\n");
}
}
int Find(MyArray *arr,int x) //查询值为x的元素,并返回下标
{
if(arr->Size==0)
{
printf("未查询到该元素");
return -1;
}
else
{
for(int i=0;i<arr->Size;i++)//遍历查询即可
{
if(arr->Element[i]==x) return i;
}
}
printf("未查询到该元素");//如果进行到这一步还找不到,那么就是不存在
return -1;
}
int FindKth(MyArray *arr,int k)//返回某个位置上的元素,位置从0开始
{
if(arr->Size>k&&k>=0)
{
return arr->Element[k];
}
else
{
printf("该位置上没有元素");
return -1;
}
}
void Insert(MyArray *arr,int k,int x) //在下标为k的地方插入x
{ //要修改这个对象,所以要传指针
if(arr->Size==arr->Length)
{
printf("内存已满,请扩容");
}
else
{
for(int i=arr->Size;i>k;i--)//k下标之后的元素向后挪一位,必须从后往前去挪。
{//如果正着拷贝那么Element[i+1]=Element[i],Element[i+1]原来的值就被覆盖掉了;
//后面Element[i+2]=Element[i+1]就没办法使用Element[i+1]的值去拷贝。
arr->Element[i+1]=arr->Element[i];
}
arr->Element[k]=x; //在k下标处空出来个位置,直接赋值插入
arr->Size++; //实际长度要+1
}
}
void Delete(MyArray *arr,int k)//在下标为k的地方删除这个元素
{
if(arr->Size==0)
{
printf("此表为空表,无法删除元素\n");
return;
}
else
{
for(int i=k+1;i<arr->Size;i++) //k下标之后的数整体向前挪动一位。
{ //正好将下标为k的元素给覆盖;
arr->Element[i-1]=arr->Element[i];
}
arr->Size--;//实际长度-1
}
}
void MakeEmpty(MyArray *arr)
{
arr->Size=0;//将实际长度置为0,相当于原有的数据都变成了脏数据。
}
int main()
{
MyArray Array;
Array=Init();
Insert(&Array,0,1);//插入1.2.3.4
Insert(&Array,1,2);
Insert(&Array,2,3);
Insert(&Array,3,4);
PrintList(&Array);//展示元素
printf("%d\n",Find(&Array,3));//查询值为3的下标,下标从0开始。
printf("%d\n",FindKth(&Array,2));//查询下标为2的下标。
Delete(&Array,2);
Delete(&Array,3);
PrintList(&Array);
MakeEmpty(&Array);
PrintList(&Array);
return 0;
}
(二)链表的实现
一次性写太多太累了,并且链表的一些细节也要写一下,留到下一次吧![]()
文章介绍了线性表这一抽象数据类型,包括其概念、操作以及两种主要实现方式——顺序存储和链式存储。通过C语言代码展示了顺序表的简单数组实现,包括初始化、输出、查找、插入和删除等操作。同时,文章强调了使用C语言作为实现语言的原因和线性表在数据结构与算法学习中的重要性。

2018

被折叠的 条评论
为什么被折叠?



