摘要:其实说实话,可能很多人依然分不清线性表,顺序表,和链表之间的区别和联系!

本文分享自华为云社区《程序员必会自己设计线性表(顺序表、链表)》,原文作者:bigsai。

前言

其实说实话,可能很多人依然分不清线性表顺序表,和链表之间的区别和联系!

  • 线性表:逻辑结构, 就是对外暴露数据之间的关系,不关心底层如何实现,数据结构的逻辑结构大分类就是线性结构和非线性结构而顺序表、链表都是一种线性表。
  • 顺序表、链表:物理结构,他是实现一个结构实际物理地址上的结构。比如顺序表就是用数组实现。而链表用指针完成主要工作。不同的结构在不同的场景有不同的区别。

在Java中,大家都知道List接口类型,这就是逻辑结构,因为他就是封装了一个线性关系的一系列方法和数据。而具体的实现其实就是跟物理结构相关的内容。比如顺序表的内容存储使用数组的,然后一个get,set,add方法都要基于数组来完成,而链表是基于指针的。当我们考虑对象中的数据关系就要考虑指针的属性。指针的指向和value。

下面用一个图来浅析线性表的关系。可能有些不太确切,但是其中可以参考,并且后面也会根据这个图举例。

线性表基本架构

对于一个线性表来说。不管它的具体实现如何,但是它们的方法函数名和实现效果应该一致(即使用方法相同、达成逻辑上效果相同,差别的是运行效率)。线性表的概念与Java的接口/抽象类有那么几分相似。最著名的就是List的Arraylist和LinkedList,List是一种逻辑上的结构,表示这种结构为线性表,而ArrayList,LinkedList更多的是一种物理结构(数组和链表)。

所以基于面向对象的编程思维,我们可以将线性表写成一个接口,而具体实现的顺序表和链表的类可以实现这个线性表的方法,提高程序的可读性,还有一点比较重要的,记得初学数据结构与算法时候实现的线性表都是固定类型(int),随着知识的进步,我们应当采用泛型来实现更合理。至于接口的具体设计如下:

package LinerList;
public interface ListInterface<T> {   void Init(int initsize);//初始化表int length();boolean isEmpty();//是否为空int ElemIndex(T t);//找到编号T getElem(int index) throws Exception;//根据index获取数据void add(int index,T t) throws Exception;//根据index插入数据void delete(int index) throws Exception;void add(T t) throws Exception;//尾部插入void set(int index,T t) throws Exception;String toString();//转成String输出
}

顺序表

顺序表是基于数组实现的,所以所有实现需要基于数组特性。对于顺序表的结构应该有一个存储数据的数组data和有效使用长度length.

还有需要注意的是初始化数组的大小,你可以固定大小,但是笔者为了可用性如果内存不够将扩大二倍。

下面着重讲解一些初学者容易混淆的概念和方法实现。这里把顺序表比作一队坐在板凳上的人。

插入操作

add(int index,T t)

其中index为插入的编号位置,t为插入的数据,插入的流程为:

(1)从后(最后一个有数据位)向前到index依次后移一位,腾出index位置的空间

(2)将待插入数据赋值到index位置上,完成插入操作

可以看得出如果顺序表很长,在靠前的地方如果插入效率比较低(插入时间复杂度为O(n)),如果频繁的插入那么复杂度挺高的。

删除操作

同理,删除也是非常占用资源的。原理和插入类似,删除index位置的操作就是从index+1开始向后依次将数据赋值到前面位置上,具体可以看这张图:

代码实现

这里我实现一个顺序表给大家作为参考学习:

package LinerList;public class seqlist<T> implements ListInterface<T> {private Object[] date;//数组存放数据private int lenth;public seqlist() {//初始大小默认为10Init(10);}public void Init(int initsize) {//初始化this.date=new Object[initsize];lenth=0;     }public int length() {      return this.lenth;}public boolean isEmpty() {//是否为空if(this.lenth==0)return true;return false;}/** * @param t * 返回相等结果,为-1为false*/public int ElemIndex(T t) {// TODO Auto-generated method stubfor(int i=0;i<date.length;i++){if(date[i].equals(t)){return i;}}return -1;}/**获得第几个元素*/public T getElem(int index) throws Exception {// TODO Auto-generated method stubif(index<0||index>lenth-1)throw new Exception("数值越界");return (T) date[index];}public void add(T t) throws Exception {//尾部插入add(lenth,t);}/**根据编号插入*/public void add(int index, T t) throws Exception {if(index<0||index>lenth)throw new Exception("数值越界");if (lenth==date.length)//扩容{Object newdate[]= new Object[lenth*2];for(int i=0;i<lenth;i++){newdate[i]=date[i];}date=newdate;}for(int i=lenth-1;i>=index;i--)//后面元素后移动{date[i+1]=date[i];}date[index]=t;//插入元素lenth++;//顺序表长度+1}public void delete(int index) throws Exception {if(index<0||index>lenth-1)throw new Exception("数值越界");for(int i=index;i<lenth;i++)//index之后元素前移动{date[i]=date[i+1];}lenth--;//长度-1  }@Overridepublic void set(int index, T t) throws Exception {if(index<0||index>lenth-1)throw new Exception("数值越界");date[index]=t;}public String  toString() {String vaString="";for(int i=0;i<lenth;i++){vaString+=date[i].toString()+" ";}return vaString;}
}

链表

学习c/c++的时候链表应该是很多人感觉很绕的东西,这个很大原因可能因为指针,Java虽然不直接使用指针但是我们也要理解指针的原理和运用。链表不同于顺序表(数组)它的结构像一条链一样链接成一个线性结构,而链表中每一个节点都存在不同的地址中,链表你可以理解为它存储了指向节点(区域)的地址,能够通过这个指针找到对应节点。

对于物理存储结构,地址之间的联系是无法更改的,相邻就是相邻。但对于链式存储,下一位的地址是上一个主动记录的,可以进行更改。这就好比亲兄弟从出生就是同姓兄弟,而我们在成长途中最好的朋友可能会由于阶段性发生一些变化!

就如西天取经的唐僧、悟空、八戒、沙和尚。他们本无联系,但结拜为师徒兄弟,你问悟空他的师父它会立马想到唐僧,因为五指山下的约定。

基本结构

对于线性表,我们只需要一个data数组和length就能表示基本信息。而对于链表,我们需要一个node(head头节点),和length分别表示存储的节点数据和链表长度,这个节点有数据域指针域。数据域就是存放真实的数据,而指针域就是存放下一个node的指针,其具体结构为:

class node<T>{T data;//节点的结果node next;//下一个连接的节点public node(){}public node(T data){this.data=data;}public node(T data, node next) {this.data = data;this.next = next;}
}

带头结点链表VS不带头结点链表

有很多人会不清楚带头结点和不带头结点链表的区别,甚至搞不懂什么是带头结点和不带头结点,我给大家阐述一下:

带头结点:head指针始终指向一个节点,这个节点不存储有效值仅仅起到一个标识作用(相当于班主任带学生)

不带头结点:head指针始终指向第一个有效节点,这个节点储存有效数值。

那么带头结点和不带头结点的链表有啥区别呢?

查找上:无大区别,带头结点需要多找一次。

插入上:非第0个位置插入区别不大,不带头结点的插入第0号位置之后需要重新改变head头的指向。

删除上:非第0个位置删除区别不大,不带头结点的删除第0号位置之后需要重新改变head头的指向。

头部删除(带头节点):带头节点的删除和普通删除一样。直接head.next=head.next.next,这样head.next就直接指向第二个元素了。第一个就被删除了

头部删除(不带头节点):不带头节点的第一个节点(head)就存储有效数据。不带头节点删除也很简单,直接将head指向链表中第二个node节点就行了。即:head=head.next

总而言之:带头结点通过一个固定的头可以使链表中任意一个节点都同等的插入、删除。而不带头结点的链表在插入、删除第0号位置时候需要特殊处理,最后还要改变head指向。两者区别就是插入删除首位(尤其插入)当然我是建议你以后在使用链表时候尽量用带头结点的链表避免不必要的麻烦。

带头指针VS带尾指针

基本上是个链表都是要有头指针的,那么头尾指针是个啥呢?

头指针: 其实头指针就是链表中head节点,成为头指针。

**尾指针: **尾指针就是多一个tail节点的链表,尾指针的好处就是进行尾插入的时候可以直接插在尾指针的后面,然后再改变一下尾指针的顺序即可。

但是带尾指针的单链表如果删除尾的话效率不高,需要枚举整个链表找到tail前面的那个节点进行删除。

插入操作

add(int index,T t)

其中index为插入的编号位置,t为插入的数据,在带头结点的链表中插入在任何位置都是等效的。
加入插入一个节点node,根据index找到插入的前一个节点叫pre。那么操作流程为

1. `node.next=pre.next`,将插入节点后面先与链表对应部分联系起来。此时node.next和pre.next一致。
2. `pre.next=node` 将node节点插入到链表中。

当然,很多时候链表需要插入在尾部,如果频繁的插入在尾部每次枚举到尾部的话效率可能比较低,可能会借助一个尾指针去实现尾部插入。

删除操作

按照index移除(主要掌握):delete(int index)

本方法为带头结点普通链表的通用方法(删除尾也一样),找到该index的前一个节点pre,pre.next=pre.next.next

代码实现

在这里我也实现一个单链表给大家作为参考使用:

package LinerList;class node<T>{T data;//节点的结果node next;//下一个连接的节点public node(){}public node(T data){this.data=data;}public node(T data, node next) {this.data = data;this.next = next;}}
public class Linkedlist<T> implements ListInterface<T>{node head;private int length;public Linkedlist() {head=new node();length=0;}public void Init(int initsize) {head.next=null;}public int length() {return this.length;}public boolean isEmpty() {if(length==0)return true;else return false;}/** 获取元素编号*/public int ElemIndex(T t) {node team=head.next;int index=0;while(team.next!=null){if(team.data.equals(t)){return index;}index++;team=team.next;}return -1;//如果找不到}@Overridepublic T getElem(int index) throws Exception {node team=head.next;if(index<0||index>length-1){throw new Exception("数值越界");}for(int i=0;i<index;i++){team=team.next;}return (T) team.data;}public void add(T t) throws Exception {add(length,t);}//带头节点的插入,第一个和最后一个一样操作public void add(int index, T value) throws Exception {if(index<0||index>length){throw new Exception("数值越界");}node<T> team=head;//team 找到当前位置nodefor(int i=0;i<index;i++){team=team.next;}node<T>node =new node(value);//新建一个nodenode.next=team.next;//指向index前位置的下一个指针team.next=node;//自己变成index位置 length++;}@Overridepublic void delete(int index) throws Exception {if(index<0||index>length-1){throw new Exception("数值越界");}node<T> team=head;//team 找到当前位置nodefor(int i=0;i<index;i++)//标记team 前一个节点{team=team.next;}//team.next节点就是我们要删除的节点team.next=team.next.next;length--;}@Overridepublic void set(int index, T t) throws Exception {// TODO Auto-generated method stubif(index<0||index>length-1){throw new Exception("数值越界");}node<T> team=head;//team 找到当前位置nodefor(int i=0;i<index;i++){team=team.next;}team.data=t;//将数值赋值,其他不变}public String toString() {String va="";node team=head.next;while(team!=null){va+=team.data+" ";team=team.next;}return va;}}

总结

你可能会问这个是否正确啊,那我来测试一下:

这里的只是简单实现,实现基本方法。链表也只是单链表。完善程度还可以优化。能力有限, 如果有错误或者优化的地方还请大佬指正

单链表查询速度较慢,因为他需要从头遍历,如果在尾部插入,可以考虑设计带尾指针的链表。而顺序表查询速度虽然快但是插入很费时费力,实际应用根据需求选择

Java中的Arraylist和LinkedList就是两种方式的代表,不过LinkedList使用双向链表优化,并且JDK也做了大量优化。所以大家不用造轮子,可以直接用,但是手写顺序表、单链表还是很有学习价值的。

点击关注,第一时间了解华为云新鲜技术~

线性表、顺序表和链表,你还分不清?相关推荐

  1. 线性表→顺序表→链表 逐个击破

    一. 线性表 1. 前言 线性表,全名为线性存储结构.使用线性表存储数据的方式可以这样理解,即 " 把所有(一对一逻辑关系的)数据用一根线儿串起来,再存储到物理空间中 ".这根线有 ...

  2. 线性表-顺序表的基本操作

    线性表的定义和特点 线性表:由n(n≥0)个数据特性相同的元素构成的有限序列 线性表中元素的个数n(n≥0)称为线性表的长度 空表:n=0 对于非空的线性表或线性结构,特点为: 存在唯一的一个被称作& ...

  3. rsa算法c语言实现_数据结构与算法之线性表-顺序表实现(C语言版本)

    原文托管在Github: https://github.com/shellhub/blog/issues/52 数据结构与算法之线性表-顺序表实现(C语言版本) 前言 数据结构与算法是一个程序员必备的 ...

  4. 数据结构一线性表 (顺序表、单链表、双链表)

    版权声明:本文为openXu原创文章[openXu的博客],未经博主允许不得以任何形式转载 文章目录 1.线性表及其逻辑结构 1.1 线性表的定义 1.2 线性表的抽象数据类型描述 2.线性表的顺序存 ...

  5. 线性表---顺序表链表

    一.线性表 1.线性表中的元素是一对一的关系,除了第一个与最后一个元素之外其他数据元素都是首尾相连的. 如果是一对多就用树来表示,如果是多对多就用网状来表示. 2.线性表的两种存储结构 顺序表:用顺序 ...

  6. 4)线性表[顺序表和链表]

    顺序表: 1 #include<iostream> 2 using namespace std; 3 4 enum error{rangeerror,underflow,overflow, ...

  7. Java实现线性表(顺序表,链表)

    顺序表: package seqTable;import java.util.ArrayList; import java.util.Scanner;public class SeqList {pri ...

  8. 线性表☞顺序表篇(7000字细致入微讲解)

     个人主页:欢迎大家光临-->沙漠下的胡杨   各位大帅哥,大漂亮  如果觉得文章对自己有帮助  可以一键三连支持博主  你的每一分关心都是我坚持的动力   ☄: 本期重点:线性表中的顺序表   ...

  9. 线性表----顺序表

    线性表的定义 线性表是具有相同数据类型的n个数据元素的有限序列, 逻辑特性 除第一个元素外,每个元素只有一个前驱,除最后一个元素外,每个元素都有一个后继 物理结构 线性表的存储结构有顺序存储结构和链式 ...

最新文章

  1. CVE-2013-2551漏洞成因与利用分析(ISCC2014 PWN6)
  2. centos7虚拟机使用docker搭建swoole环境
  3. Android FM模块学习之一 FM启动流程
  4. php on duplicate key,php – ON DUPLICATE KEY UPDATE值;两次插入相同的值
  5. react中遇到的问题
  6. [No0000A6]Visual Studio 2015 中的常用命令的默认键盘快捷键-VS2015 Shortcut
  7. 关于Oracle与MySQL的使用总结
  8. python的接口实现zope.interface示例
  9. 去哪儿-22-async-components
  10. 服务器部署ssl证书,Apache服务器SSL证书部署
  11. 【Java】JSON数据交换格式及其使用案例(聊天工具)
  12. Linux 的简单钩子
  13. PHP openssl加密扩展使用总结
  14. java jsonarray 拷贝_JSONObject与JSONArray使用
  15. Eclipse常用开发插件(转)
  16. webStorm汉化
  17. 倾斜摄影三维模型OSGB格式简化(压缩、优化)
  18. 浏览器播放视频时蓝牙耳机自动关机
  19. Adobe Dreamweaver CS6(或者CC 2018.2 SP)安装失败解决方案
  20. 简单人物画像_超级简单人物素描画图片精选

热门文章

  1. JavaScript类型转换的有趣应用
  2. JavaScript之call,bind,apply方法及 this 的用法辨析
  3. es6 Symbol 的内置属性
  4. android h5选择图片上传,js-微信H5选择多张图片预览并上传(兼容ios,安卓,已测试)...
  5. 求两个链表的第一个公共结点各种情况及三种思路分析
  6. JAVA中的适配器应用_Java适配器模式详解和实际应用.md
  7. java实现蛇蛇大作战_蛇蛇大作战3D旋涡版
  8. 谈一下对c语言程序设计,谈《C语言程序设计》课程教学.doc
  9. oracle jinitiator 1.1.8.2,oracle jinitiator 1.1.8.2-Oracle Jinitiator1.1.8.27 3264位最新版下载_东坡手机下载...
  10. 【LeetCode】79-单词搜索