前言
数据结构,一门数据处理的艺术,精巧的结构在一个又一个算法下发挥着他们无与伦比的高效和精密之美,在为信息技术打下坚实地基的同时,也令无数开发者和探索者为之着迷。
也因如此,它作为博主大二上学期最重要的必修课出现了。由于大家对于上学期C++系列博文的支持,我打算将这门课的笔记也写作系列博文,既用于整理、消化,也用于同各位交流、展示数据结构的美。
此系列文章,将会分成两条主线,一条“数据结构基础”,一条“数据结构拓展”。“数据结构基础”主要以记录课上内容为主,“拓展”则是以课上内容为基础的更加高深的数据结构或相关应用知识。
欢迎关注博主,一起交流、学习、进步,往期的文章将会放在文末。
这一节我们将开始总结一个常见的数据结构:线性表。
由于线性表的内容较多,本节只能总结部分内容。
主要为两种基础的线性表,数组和链表。在简单介绍其定义之后,重点会放在实现对其各种操作的简单实现及封装。
由于内容较多,本篇截止到数组的操作封装完毕。链表的内容会放在之后的文章中
本节思维导图如下:
线性表的定义
线性表是较为常见的线性数据结构,又称为有序表(Ordered List)。简单点说,线性表就是节点的有限序列。
使用上一节的数据的逻辑结构的定义,可以从教科书上找到如下线性表定义:
一个线性表是由0个或多个具有相同类型的节点组成的有序集合。这里用(a1,a2,a3...an)来表示一个线性表,n为自然数。
当n=0时,线性表中无节点,称为空表;
当n≥1时,称a1位线性宝的表头(head),称an为线性表的表尾(tail);
当n≥2时,称ai为ai+1的前驱节点,称ai+1是ai的后继节点,其中1≤i<n;
表头结点无前驱节点,标为节点无后继节点;当n=1是,线性表中仅有一个节点时,该点既是表头又是表尾。
我们常用list来表示一个线性表,根据存储结构的不同,线性表又可分为顺序表和链表。Java中就使用ArrayList和LinkedList来分别表示这两种线性表。本文中的各种封装也基于这种命名
线性表的基本操作
对线性表的基本操作有很多,给他们分分类,可以分成
- 线性表本身操作:的创建、遍历、规模及是否为空等访问
- 数据的操作:插入、删除、修改
- 数据的查询:查询元素,查询下标等查询方式
下面的内容将会重点放在这些基本操作的实现及分析上。
顺序存储结构——数组
常见的线性表中最最常见的存储结构——顺序存储。这样的线性表又称作顺序表,其中各元素不仅在逻辑上是线性相邻的,在空间中也是连续的。
我们最常用的数组就是这样一种顺序表。
数组的基本操作及封装
下面就来分析一下数组如何实现基本操作并进行封装
创建顺序表
创建数组是最基础的操作了。
在C语言中,我们可以直接在栈空间中创建一个数组,也可以动态申请一一片内存在堆空间中创建一个数组。以整形数组为例,对应的操作如下:
//假设数组规模为N
int array[N];
int *array = malloc(N * sizeof(int));
在Java语言中,只有数组的头引用是在栈区中创建的,数组的体则都在堆区中。同样以整形为例,操作如下:
//假设数组规模为N
int[] array = new int[N];
遍历顺序表
遍历一个数组也是最基本的操作,由于数组中元素的个数通常是不定的,也就是变量,所以遍历数组的操作通常使用循环来完成。同样以整形数组为例,C和Java使用for循环遍历数组的基础格式如下:
//假设数组中元素个数为n
for(int i = 0;i < n;i++){
array[i];
}
Java中还提供了增强for循环用以遍历数组:
for(int k : array){
k;
}
复杂度:O(n)
查询下标为k的元素
对于顺序表,也就是数组,按照下标查找元素可以说是他最得心应手的操作了。由于数据都是按照顺序且相邻存储的,访问确定下标的元素仅需要直接根据首地址进行加法运算即可获得对应下标的元素。
我们假设要访问的下标为k(k从0开始计数)
则在C语言中,可以直接使用指针运算获得下标为k的元素,可以以使用中括号
Java中没有指针,直接使用中括号即可
以整形数组array
为例,其长度为len
,当前元素个数为size
,查询位置为k
。
//C
int selectByIndex(int * array,int size,int k){
if(k < 0 || k > size)
exit(0);
return array[k];
}
//java
class ArrayList{
int[] array;
int len;
int size;
public int selectByIndex(int k) throws Exception{
if(k < 0 || k > size)
throw new Exception();
return array[k];
}
}
复杂度:O(1)
查询特定的元素
同样作为查询,查询特定元素的复杂性就要高于按照下标查询。原因在于线性表中的下标是有顺序的且各元素占位等长,对应下标可以计算。但是元素内容却没有顺序,因此只能遍历整个数组进行查找。
以整形数组array
为例,查询值为value
的元素的下标,数组长度为len
,未查找到该元素则返回-1
//C
int selectByValue(int value,int * array,int size){
for(int i = 0;i < size;i++){
if(array[i] == value)
return i;
}
return -1;
}
//Java
class ArrayList{//线性表类
int[] array;
int len;
public int selectByValue(int value){
for(int i = 0;i < len;i++){
if(array[i] == value)
return i;
}
return -1;
}
}
复杂度:O(n)
(注:由于C语言和Java的编程思想不同(面向过程和面向对象),同样的算法可能会以不同的形式被封装在不同的结构下,C语言的函数往往会按照功能封装在一个文件中调用时需传入要处理的对象,Java则会将函数作为方法封装在对象节点内部。下文的各个方法封装都会有如此差别)
插入数据
插入一条数据通常指插入数据到指定位置,如插入到第k位。
对于顺序表,插入一条数据需要先将该位置后面的元素统一移动一格为新元素腾出空间,再将新元素插入其中。
以整形数组array
为例,其长度为len
,当前元素个数为size
,插入元素为value
,插入位置为k
。
//C
int insert(int * array,int len,int size,int value,int k){
if(len == size || k < 0 || k > len)
return 0;
for(int i = len;i > k;i--){
array[i] = array[i - 1];
}
array[k] = value;
return 1;
}
//java
class ArrayList{
int[] array;
int len;
int size;
public boolean insert(int value,int k){
if(len == size || k < 0 || k > len)
return false;
for(int i = len;i > k;i--){
array[i] = array[i - 1];
}
array[k] = value;
size++;
return true;
}
}
复杂度:O(n)
删除数据
删除某个数据的前提是检索到该数据,所以也可以分成删除某下标的数据和删除某个数据。
考虑到删除某个数据也可以分解成先查询某数据的下标再删除该下标对应的数据,因此只需要实现删除某下标的数据即可。
在顺序表中,直接删除某下标的数据会导致空缺,从而破坏顺序表的存储连续性。因此在删除某个元素之后还需要对数组进行维护,维护的操作就是将被删除的元素后面的元素依次向前移动。对于整形数组,可直接采用覆盖代替删除。
以整形数组array
为例,其长度为len
,当前元素个数为size
,删除位置为k
。
//C
int remove(int * array,int size,int k){
if(k < 0 || k > size)
return 0;
for(int i = k;i < size - 1;i++){
array[i] = array[i + 1];
}
return 1;
}
//Java
class ArrayList{
int[] array;
int len;
int size;
public boolean remove(int k){
if(k < 0 || k > len)
return false;
for(int i = k;i < size - 1;i--){
array[i] = array[i + 1];
}
size--;
return true;
}
}
复杂度:O(n)
修改数据
同删除数据类似的,修改数据也面临着筛选数据的问题。因此也只需要我们封装好修改第k位数据的功能即可。
数组的优点就在于可以直接按照下标进行访问,因此对于修改制定下标的元素,数组的效率是非常高的。
以整形数组array
为例,其长度为len
,当前元素个数为size
,修改值为value
,修改位置为k
。
//C
int update(int * array,int size,int value,int k){
if(k < 0 || k > size)
return 0;
array[k] = value;
return 1;
}
//java
class ArrayList{
int[] array;
int size;
int len;
public boolean update(int k,int value){
if(k < 0 || k > size)
return false;
array[k] = value;
return true;
}
}
复杂度:O(1)
顺序表的封装(以整形为例)
C
/*头文件*/
#ifndef ARRAYLIST_H_INCLUDED
#define ARRAYLIST_H_INCLUDED
#include<malloc.h>
int * createArray(int);//创建数组
int deleteArray(int*);//释放数组
int update(int*,int,int,int);//修改元素
int remove(int*,int,int);//删除元素
int insert(int*,int,int,int,int);//插入元素
int selectByValue(int*,int,int);//查找特定元素
int selectByIndex(int*,int,int);//根据下标查找元素
#endif // ARRAYLIST_H_INCLUDED
/*源文件*/
#include "ArrayList.h"
int * createArray(int len){
return (int*) malloc(len * sizeof(int));
}
int deleteArray(int* array){
free(array);
}
int update(int * array,int size,int value,int k){
if(k < 0 || k > size)
return 0;
array[k] = value;
return 1;
}
int remove(int * array,int size,int k){
if(k < 0 || k > size)
return 0;
for(int i = k;i < size - 1;i++){
array[i] = array[i + 1];
}
return 1;
}
int insert(int * array,int len,int size,int value,int k){
if(len == size || k < 0 || k > len)
return 0;
for(int i = len;i > k;i--){
array[i] = array[i - 1];
}
array[k] = value;
return 1;
}
int selectByValue(int * array,int value,int size){
for(int i = 0;i < size;i++){
if(array[i] == value)
return i;
}
return -1;
}
int selectByIndex(int * array,int size,int k){
if(k < 0 || k > size)
exit(0);
return array[k];
}
Java
public class ArrayList {
int[] array;
int len;
int size;
public ArrayList(int len) throws Exception {
if(len <= 0) {
throw new Exception();
}
this.len = len;
array = new int[len];
size = 0;
}
public int selectByIndex(int k) throws Exception{
if(k < 0 || k > size)
throw new Exception();
return array[k];
}
public int selectByValue(int value){
for(int i = 0;i < len;i++){
if(array[i] == value)
return i;
}
return -1;
}
public boolean insert(int value,int k){
if(len == size || k < 0 || k > len)
return false;
for(int i = len;i > k;i--){
array[i] = array[i - 1];
}
array[k] = value;
size++;
return true;
}
public boolean remove(int k){
if(k < 0 || k > len)
return false;
for(int i = k;i < size - 1;i--){
array[i] = array[i + 1];
}
size--;
return true;
}
public boolean update(int k,int value){
if(k < 0 || k > size)
return false;
array[k] = value;
return true;
}
}
往期博客
参考资料:
- 《数据结构》(刘大有,杨博等编著)
- 《算法导论》(托马斯·科尔曼等编著)
- 《图解数据结构——使用Java》(胡昭民著)