原创公众号:bigsai 文章收藏在 GitHub
经过前面数据结构与算法前导我么知道了数据结构的一些概念和重要性,那么咱们今天总结下线性表相关的内容。固然,我用本身的理解解
分享给你们。java
其实说实话,可能不少人依然分不清线性表,顺序表,和链表
之间的区别和联系!node
逻辑结构
, 就是对外暴露数据之间的关系,不关心底层如何实现。物理结构
,他是实现一个结构实际物理地址上的结构。好比顺序表就是用数组
实现。而链表用指针
完成主要工做。不一样的结构在不一样的场景有不一样的区别。对于java来讲,你们都知道List
接口类型,这就是逻辑结构,由于他就是封装了一个线性关系的一系列方法和数据。而具体的实现其实就是跟物理结构相关的内容。好比顺序表的内容存储使用数组的,而后一个get,set,add方法都要基于数组
来完成,而链表是基于指针
的。当咱们考虑对象中的数据关系就要考虑指针
的属性。指针的指向和value。git
下面用一个图来浅析线性表的关系。可能有些不太确切,可是其中能够参考,而且后面也会根据这个图举例。github
对于一个线性表
来讲。无论
它的具体实现
方法如何,咱们应该有的函数名称
和实现效果
应该一致。你也能够感受的到在一些结构的设计。好比List的Arraylist
和LinkedList
。Map的HashMap和currentHashMap他们的接口api都是相同的,可是底层设计实现确定是有区别的。算法
因此,基于面向对象的编程思惟,咱们能够将线性表写成一个接口,而具体实现的顺序表和链表能够继承
这个接口的方法,提升程序的可读性。编程
还有一点比较重要的,记得初学数据结构与算法时候实现的线性表都是固定类型
(int),随着知识的进步,咱们应当采用泛型
来实现更合理。至于接口的具体设计以下:api
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为插入的数据
-根据图片你就很好理解插入操做。当插入一个index时候,他的后面全部元素都要后移一位。你能够看的出插入时候整个操做的臃肿性。因此这也是顺序表性能表现最差
的地方,频繁的插入,删除。
同理,删除也是很是占用资源的。原理和插入相似,不过人走了,空一个小板凳
后面的人须要往前挪
。
其余操做就很简单了。好比若是按照编号获取数据getElem(int index)
,你能够直接根据数据坐标返回。a[index],而其余操做,能够经过遍历直接操做数组
便可。
我想,表应该是不少人感受很绕的东西,这个很大缘由可能由于指针
。不少人说java
没指针,其实java他也有隐形指针。只不过不能直接用罢了。
指针创建的数据关系每每比数组这些要抽象的多。对于指针域,你把他当成一个对象就行了,不过这个对象指向的是另外一个同等级对象
。对于这个关系,你能够比做每一个person类。每一个person类都有老公(老婆)
,而这个老公老婆也是一个实际对象,能够理解这更像一种逻辑约定关系
,而不是硬生生的关系吧。
指针你能够考虑成脑子记忆
。上面的顺序表咱们说它有序由于每一个小板凳(数组)有编号
,咱们能够根据这个来肯定位置。而对于链表来讲,你能够看做成一个站在操场上的一队人。而他的操做也略有不一样,下面针对一些比较特殊和重要的进行概括。
对于线性表,咱们只须要一个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; } }
固然,这个节点有数据域
和指针域
。数据域就是存放真实的数据,而指针域就是存放下一个node的指针。因此相比顺序表,若是用满数组状况下,链表占用更多的资源,由于它要存放指针占用资源。
add(int index,T t)
其中index为插入的编号位置,t为插入的数据
加入插入一个节点node
,根据index找到插入的前一个节点叫pre
。那么操做流程为
node.next=pre.next
以下1的操做,将插入节点后面联系起来。此时node.next和pre.next一致。pre.next=node
由于咱们要插入node,而node链能够替代pre自身的next。那么直接将pre指向node。那么就至关于原始链表插入了一个node。
不少人搞不清什么是带头节点
和不带头节点
。带头节点就是head节点不放数据,第0项从head后面那个开始数。而不带头节点的链表head放数据,head节点就是第0位
。
主要区别:
可看做一列火车的火车头
)。而方便的就是带头节点在首位插入更简单。由于插入第0位
也是在head的后面
。node才是
插入后最长链的首位节点
,head在他的后面,而在链表中head一般表示首位节点
,因此head不表示第二个节点,直接"="node节点
。这样head和node都表示操做完成的链表。可是对外暴露的只有head。因此head只能指向第一个节点!)next
为null
。不能和插入普通位置相比!
按照index移除:delete(int index)
按照尾部移除(拓展):deleteEnd()
这个方法我没有写,可是我给你们讲一下,按照尾部删除的思想就是:
node.next!=null
时node=node.next
指向下一个node.next==null
时候。说明这个节点时最后一个。你能够node=null
。这个这个node的前驱pre的next就是null。这个节点就被删除了。头部删除(带头节点):
head.next(第1个元素)
=head.next.next(第二个元素)
头部删除(不带头节点)
第一个
就是存货真价实的元素
的。不带头节点删除也很简单。直接将head移到第二位
就好了。即:head=head.next
从head开始
。而后另外一个节点team=head
或head.next
。而后用这个节点不停的等于它指向的next去查找咱们须要的内容即while(循环条件){team=team.next}相似。if else
.package LinerList; public class seqlist<T> implements ListInterface<T> { private Object[] date;//数组存放数据 private int lenth; public seqlist() {//初始大小默认为10 Init(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 stub for(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 stub if(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 } @Override public 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; } }
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;//若是找不到 } @Override public 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 找到当前位置node for(int i=0;i<index;i++) { team=team.next; } node<T>node =new node(value);//新建一个node node.next=team.next;//指向index前位置的下一个指针 team.next=node;//本身变成index位置 length++; } @Override public void delete(int index) throws Exception { if(index<0||index>length-1) { throw new Exception("数值越界"); } node<T> team=head;//team 找到当前位置node for(int i=0;i<index;i++)//标记team 前一个节点 { team=team.next; } //team.next节点就是咱们要删除的节点 team.next=team.next.next; length--; } @Override public void set(int index, T t) throws Exception { // TODO Auto-generated method stub if(index<0||index>length-1) { throw new Exception("数值越界"); } node<T> team=head;//team 找到当前位置node for(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; } }
package LinerList; public class test { public static void main(String[] args) throws Exception { // TODO Auto-generated method stub System.out.println("线性表测试:"); ListInterface<Integer>list=new seqlist<Integer>(); list.add(5); list.add(6); list.add(1,8); list.add(3,996); list.add(7); System.out.println(list.ElemIndex(8)); System.out.println(list.toString()); list.set(2, 222); System.out.println(list.toString()); list.delete(4); System.out.println(list.toString()); System.out.println(list.length()); System.out.println("链表测试:"); list=new Linkedlist<Integer>(); list.add(5); list.add(6); list.add(1,8); list.add(3,996); list.add(7); System.out.println(list.ElemIndex(8)); System.out.println(list.toString()); list.set(2, 222); System.out.println(list.toString()); list.delete(4); System.out.println(list.toString()); System.out.println(list.length()); } }
输出:
线性表测试:
1
5 8 6 996 7
5 8 222 996 7
5 8 222 996
4
链表测试:
1
5 8 6 996 7
5 222 6 996 7
5 222 6 996
4
这里的只是简单实现,实现基本方法。链表也只是单链表。完善程度还能够优化。若是有错误还请大佬指正。
单链表查询速度较慢
,由于他须要从头遍历。若是频繁操做尾部,能够考虑链表中不只有head在加尾tail
节点。而顺序表查询速度虽然快可是插入很费时费力。实际应用根据需求选择!
java中的Arraylist和LinkedList就是两种方式的表明,不过LinkedList使用双向链表优化,而且jdk的api作了大量优化。因此你们不用造轮子
,能够直接用,可是仍是颇有学习价值
的。
若是有不理解或者不懂的能够联系交流讨论。
原创不易,bigsai请朋友们帮两件事帮忙一下:
我们下次再见!