线性表 及Java实现 顺序表、链表、栈、队列
数据结构与算法是程序设计的两大基础,大型的IT企业面试时也会出数据结构和算法的题目,
它可以说明你是否有良好的逻辑思维,如果你具备良好的逻辑思维,即使技术存在某些缺陷,面试公司也会认为你很有培养价值,至少在一段时间之后,技术可以很快得到提高。同时,它也是软考的重点,我们需要对这部分的内容进行一下总结。
我们先看一下数据结构和算法的整体内容。
1、线性表
概念:
数据元素的排列方式是线性的。
分类:
分类规则是根据上图中元素的存储结构来划分的。
(1)顺序表
基本思想:元素的存储空间是连续的。在内存中是以顺序存储,内存划分的区域是连续的。存储结构如下图:
(2)链表
基本思想:元素的存储空间是离散的,单独的(物理),它们可以通过在逻辑上指针的联系使得它成为了整体的链表。存储结构如下图:
1.单链表
2.循环链表
·
3.双链表(双向循环表)
(图有点小问题 :最后一个节点的 指针域 也指向头结点)
三者的区别(从上面三个图我们可以总结出来):
1、它们都有数据域(data(p))和指针域(next(p)),但是从图中可以看出双链表有两个指针域,一个指向它的前节点,一个指向它的后节点。
2、单链表最后一个节点的指针域为空,没有后继节点;循环链表和双链表最后一个节点的指针域指向头节点,下一个结点为头节点,构成循环;
3、单链表和循环链表只可向一个方向遍历;双链表和循环链表,首节点和尾节点被连接在一起,可视为“无头无尾”;双链表可以向两个方向移动,灵活度更大。
线性表操作:
理解了顺序表和链表的基本思想之后,线性表的操作是简单,并且网上有很多讲解插入和删除结点的博客,在这里我就不过多的介绍了。
顺序表和链表的对比:
栈和队列是特殊的线性表,既然特殊就有不同点。
2、栈
基本思想:后进先出(先进后出)即栈中元素被处理时,按后进先出的顺序进行,栈又叫后进先出表(LIFO)。
举例:
日常生活中有很多栈的例子。例如,放在书桌上的一摞书,只能从书顶上拿走一本书,书也只能放在顶上。如下图所示:
3、队列
基本思想:先进先出即先被接收的元素将先被处理,又叫先进先出表(FIFO)。如下图所示:
举例:
队列的例子,生活中更多。比如:买车票排队,排头最先买到车票,新来的排的队尾;进车站时,安检行李,先进去的最先出来,后进去的后出来。
分类:
1.顺序队列
如下图所示:
顺序队列的操作,要判断队满和队空的标志,从图中我们可以总结得到:
1.队空:head = tail
2.队满:tail = m
2.循环队列
如下图所示:
循环队列的操作,要判断队空和队满的情况,从图中我们可以总结得到:
1.队空:head = tail
2.队满:tail + 1 = head(在队列中会留一个空着的空间,所以要加1)
总结
线性表真的很简单。
----------------------------------------------------------------------------------------------------
数据结构中的线性表,对应着Collection接口中的List接口。
在本节中,我们将做以下三件事
第一。我们先来看看线性表的特征
第二,自己用JAVA实现List
第三,对比的线性表、链式表性能,以及自己的List性能与JDKList性能对比
线性表特征:
第一,一个特定的线性表,应该是用来存放特定的某一个类型的元素的(元素的“同一性”)
第二, 除第一个元素外,其他每一个元素有且仅有一个直接前驱;除最后一个元素外,其他每一个元素有且仅有一个直接后继(元素的“序偶性”)
第三, 元素在线性表中的“下标”唯一地确定该元素在表中的相对位置(元素的“索引性”)
又,一.线性表只是数据的一种逻辑结构,其具体存储结构可以为顺序存储结构和链式储存结构来完成,对应可以得到顺序表和链表,
二.对线性表的入表和出表顺序做一定的限定,可以得到特殊的线性表,栈(FILO)和队列(FIFO)
自己实现线性表之顺序表
思路:
1. 顺序表因为采用顺序存储形式,所以内部使用数组来存储数据
2.因为存储的具体对象类型不一定,所以采用泛型操作
3.数组操作优点:1.通过指针快速定位到下表,查询快速
缺点:1.数组声明时即需要确定数组大小。当操作中超过容量时,则需要重新声明数组,并且复制当前所有数据
2.当需要在中间进行插入或者删除时,则需要移动大量元素(size-index个)
具体实现代码如下
- /**
- * 自己用数组实现的线性表
- */
- public class ArrayList<E> {
- Object[] data = null;// 用来保存此队列中内容的数组
- int current; // 保存当前为第几个元素的指标
- int capacity; // 表示数组大小的指标
- /**
- * 如果初始化时,未声明大小,则默认为10
- */
- public ArrayList() {
- this(10);
- }
- /**
- * 初始化线性表,并且声明保存内容的数组大小
- * @param initalSize
- */
- public ArrayList(int initalSize) {
- if (initalSize < 0) {
- throw new RuntimeException("数组大小错误:" + initalSize);
- } else {
- this.data = new Object[initalSize];
- this.current = 0;
- capacity = initalSize;
- }
- }
- /**
- * 添加元素的方法 添加前,先确认是否已经满了
- * @param e
- * @return
- */
- public boolean add(E e) {
- ensureCapacity(current);// 确认容量
- this.data[current] = e;
- current++;
- return true;
- }
- /**
- * 确认系统当前容量是否满足需要,如果满足,则不执行操作 如果不满足,增加容量
- * @param cur 当前个数
- */
- private void ensureCapacity(int cur) {
- if (cur == capacity) {
- // 如果达到容量极限,增加10的容量,复制当前数组
- this.capacity = this.capacity + 10;
- Object[] newdata = new Object[capacity];
- for (int i = 0; i < cur; i++) {
- newdata[i] = this.data[i];
- }
- this.data = newdata;
- }
- }
- /**
- * 得到指定下标的数据
- * @param index
- * @return
- */
- public E get(int index) {
- validateIndex(index);
- return (E) this.data[index];
- }
- /**
- * 返回当前队列大小
- * @return
- */
- public int size() {
- return this.current;
- }
- /**
- * 更改指定下标元素的数据为e
- * @param index
- * @param e
- * @return
- */
- public boolean set(int index, E e) {
- validateIndex(index);
- this.data[index] = e;
- return true;
- }
- /**
- * 验证当前下标是否合法,如果不合法,抛出运行时异常
- * @param index 下标
- */
- private void validateIndex(int index) {
- if (index < 0 || index > current) {
- throw new RuntimeException("数组index错误:" + index);
- }
- }
- /**
- * 在指定下标位置处插入数据e
- * @param index 下标
- * @param e 需要插入的数据
- * @return
- */
- public boolean insert(int index, E e) {
- validateIndex(index);
- Object[] tem = new Object[capacity];// 用一个临时数组作为备份
- //开始备份数组
- for (int i = 0; i < current; i++) {
- if (i < index) {
- tem[i] = this.data[i];
- }else if(i==index){
- tem[i]=e;
- }else if(i>index){
- tem[i]=this.data[i-1];
- }
- }
- this.data=tem;
- return true;
- }
- /** * 删除指定下标出的数据<br>
- * @param index<br>
- * @return<br>
- */
- public boolean delete(int index){
- validateIndex(index);
- Object[] tem = new Object[capacity];// 用一个临时数组作为备份
- //开始备份数组
- for (int i = 0; i < current; i++) {
- if (i < index) {
- tem[i] = this.data[i];
- }else if(i==index){
- tem[i]=this.data[i+1];
- }else if(i>index){
- tem[i]=this.data[i+1];
- }
- }
- this.data=tem;
- return true;
- }
- }
自己实现线性表之链表
思路:1.链表采用链式存储结构,在内部只需要将一个一个结点链接起来。(每个结点中有关于此结点下一个结点的引用)
链表操作优点:1.因为每个结点记录下个结点的引用,则在进行插入和删除操作时,只需要改变对应下标下结点的引用即可
缺点:1.要得到某个下标的数据,不能通过下标直接得到,需要遍历整个链表。
实现代码如下
- /**
- * 自己用链式存储实现的线性表
- */
- public class LinkedList<E> {
- private Node<E> header = null;// 头结点
- int size = 0;// 表示数组大小的指标
- public LinkedList() {
- this.header = new Node<E>();
- }
- public boolean add(E e) {
- if (size == 0) {
- header.e = e;
- } else {
- // 根据需要添加的内容,封装为结点
- Node<E> newNode = new Node<E>(e);
- // 得到当前最后一个结点
- Node<E> last = getNode(size-1);
- // 在最后一个结点后加上新结点
- last.addNext(newNode);
- }
- size++;// 当前大小自增加1
- return true;
- }
- public boolean insert(int index, E e) {
- Node<E> newNode = new Node<E>(e);
- // 得到第N个结点
- Node<E> cNode = getNode(index);
- newNode.next = cNode.next;
- cNode.next = newNode;
- size++;
- return true;
- }
- /**
- * 遍历当前链表,取得当前索引对应的元素
- *
- * @return
- */
- private Node<E> getNode(int index) {
- // 先判断索引正确性
- if (index > size || index < 0) {
- throw new RuntimeException("索引值有错:" + index);
- }
- Node<E> tem = new Node<E>();
- tem = header;
- int count = 0;
- while (count != index) {
- tem = tem.next;
- count++;
- }
- return tem;
- }
- /**
- * 根据索引,取得该索引下的数据
- *
- * @param index
- * @return
- */
- public E get(int index) {
- // 先判断索引正确性
- if (index >= size || index < 0) {
- throw new RuntimeException("索引值有错:" + index);
- }
- Node<E> tem = new Node<E>();
- tem = header;
- int count = 0;
- while (count != index) {
- tem = tem.next;
- count++;
- }
- E e = tem.e;
- return e;
- }
- public int size() {
- return size;
- }
- /**
- * 设置第N个结点的值
- *
- * @param x
- * @param e
- * @return
- */
- public boolean set(int index, E e) {
- // 先判断索引正确性
- if (index > size || index < 0) {
- throw new RuntimeException("索引值有错:" + index);
- }
- Node<E> newNode = new Node<E>(e);
- // 得到第x个结点
- Node<E> cNode = getNode(index);
- cNode.e = e;
- return true;
- }
- /**
- * 用来存放数据的结点型内部类
- */
- class Node<e> {
- private E e;// 结点中存放的数据
- Node<E> next;// 用来指向该结点的下一个结点
- Node() { }
- Node(E e) {
- this.e = e;
- }
- // 在此结点后加一个结点
- void addNext(Node<E> node) {
- next = node;
- }
- }
- }
自己实现线性表之栈
栈是限定仅允许在表的同一端(通常为“表尾”)进行插入或删除操作的线性表。
允许插入和删除的一端称为栈顶(top),另一端称为栈底(base)
特点:后进先出 (LIFO)或,先进后出(FILO)
因为栈是限定线的线性表,所以,我们可以调用前面两种线性表,只需要对出栈和入栈操作进行设定即可
具体实现代码
- /**
- * 自己用数组实现的栈
- */
- public class ArrayStack<E> {
- private ArrayList<E> list=new ArrayList<E>();//用来保存数据线性表<br> private int size;//表示当前栈元素个数
- /**
- * 入栈操作
- * @param e
- */
- public void push(E e){
- list.add(e);
- size++;
- }
- /**
- * 出栈操作
- * @return
- */
- public E pop(){
- E e= list.get(size-1);
- size--;
- return e;
- }
- }
至于用链表实现栈,则只需要把保存数据的顺序表改成链表即可,此处就不给出代码了
自己实现线性表之队列
与栈类似
队列是只允许在表的一端进行插入,而在另一端删除元素的线性表。
在队列中,允许插入的一端叫队尾(rear),允许删除的一端称为队头(front)。
特点:先进先出 (FIFO)、后进后出 (LILO)
同理,我们也可以调用前面两种线性表,只需要对队列的入队和出队方式进行处理即可
- package cn.javamzd.collection.List;
- /**
- * 用数组实现的队列
- */
- public class ArrayQueue<E> {
- private ArrayList<E> list = new ArrayList<E>();// 用来保存数据的队列
- private int size;// 表示当前栈元素个数
- /**
- * 入队
- * @param e
- */
- public void EnQueue(E e) {
- list.add(e);
- size++;
- }
- /**
- * 出队
- * @return
- */
- public E DeQueue() {
- if (size > 0) {
- E e = list.get(0);
- list.delete(0);
- return e;
- }else{
- throw new RuntimeException("已经到达队列顶部");
- }
- }
- }
对比线性表和链式表
前面已经说过顺序表和链式表各自的特点,这里在重申一遍
数组操作优点:1.通过指针快速定位到下标,查询快速
缺点: 1.数组声明时即需要确定数组大小。当操作中超过容量时,则需要重新声明数组,并且复制当前所有数据
2.当需要在中间进行插入或者删除时,则需要移动大量元素(size-index个)
链表操作优点:1.因为每个结点记录下个结点的引用,则在进行插入和删除操作时,只需要改变对应下标下结点的引用即可
缺点:1.要得到某个下标的数据,不能通过下标直接得到,需要遍历整个链表。
现在,我们通过进行增删改查操作来感受一次其效率的差异
思路:通过两个表,各进行大数据量操作(3W)条数据的操作,记录操作前系统时间,操作后系统时间,得出操作时间
实现代码如下
- package cn.javamzd.collection.List;
- public class Test {
- /**
- * @param args
- */
- public static void main(String[] args) {
- //测试自己实现的ArrayList类和Linkedlist类添加30000个数据所需要的时间
- ArrayList<String> al = new ArrayList<String>();
- LinkedList<String> ll = new LinkedList<String>();
- Long aBeginTime=System.currentTimeMillis();//记录BeginTime
- for(int i=0;i<30000;i++){
- al.add("now"+i);
- }
- Long aEndTime=System.currentTimeMillis();//记录EndTime
- System.out.println("arrylist add time--->"+(aEndTime-aBeginTime));
- Long lBeginTime=System.currentTimeMillis();//记录BeginTime
- for(int i=0;i<30000;i++){
- ll.add("now"+i);
- }
- Long lEndTime=System.currentTimeMillis();//记录EndTime
- System.out.println("linkedList add time---->"+(lEndTime-lBeginTime));
- //测试JDK提供的ArrayList类和LinkedList类添加30000个数据所需要的世界
- java.util.ArrayList<String> sal=new java.util.ArrayList<String>();
- java.util.LinkedList<String> sll=new java.util.LinkedList<String>();
- Long saBeginTime=System.currentTimeMillis();//记录BeginTime
- for(int i=0;i<30000;i++){
- sal.add("now"+i);
- }
- Long saEndTime=System.currentTimeMillis();//记录EndTime
- System.out.println("JDK arrylist add time--->"+(saEndTime-saBeginTime));
- Long slBeginTime=System.currentTimeMillis();//记录BeginTime
- for(int i=0;i<30000;i++){
- sll.add("now"+i);
- }
- Long slEndTime=System.currentTimeMillis();//记录EndTime
- System.out.println("JDK linkedList add time---->"+(slEndTime-slBeginTime));
- }
- }
得到测试结果如下:
arrylist add time--->446 linkedList add time---->9767 JDK arrylist add time--->13 JDK linkedList add time---->12 |
由以上数据,我们可知:
1.JDK中的ArrayList何LinkedList在添加数据时的性能,其实几乎是没有差异的
2.我们自己写的List的性能和JDK提供的List的性能还是存在巨大差异的
3.我们使用链表添加操作,花费的时间是巨大的,比ArrayList都大几十倍
第三条显然是跟我们最初的设计不相符的,按照我们最初的设想,链表的添加应该比顺序表更省时
查看我们写的源码,可以发现:
我们每次添加一个数据时,都需要遍历整个表,得到表尾,再在表尾添加,这是很不科学的
现改进如下:设立一个Node<E>类的成员变量end来指示表尾,这样每次添加时,就不需要再重新遍历得到表尾
改进后add()方法如下
- public boolean add(E e) {
- if (size == 0) {
- header.e = e;
- } else {
- // 根据需要添加的内容,封装为结点
- Node<E> newNode = new Node<E>(e);
- //在表尾添加元素
- last.addNext(newNode);
- //将表尾指向当前最后一个元素
- last = newNode;
- }
- size++;// 当前大小自增加1
- return true;
- }
ArrayList添加的效率和JDK中对比起来也太低
分析原因为:
每次扩大容量时,扩大量太小,需要进行的复制操作太多
现在改进如下:
每次扩大,则扩大容量为当前的三倍,此改进仅需要更改ensureCapacity()方法中的一行代码,此处就不列出了。
改进后,再次运行添加元素测试代码,结果如下:
arrylist add time--->16 linkedList add time---->8 JDK arrylist add time--->7 JDK linkedList add time---->7 |
虽然还有改进的空间,但是显然,我们的效果已经大幅度改进了,而且也比较接近JDK了
接下来测试插入操作的效率
我们只需要将测试代码中的添加方法(add())改成插入方法(insert(int index,E e)),为了使插入次数尽可能多,我们把index都设置为0
测试结果如下:
arrylist inset time--->17 linkedList inset time---->13 JDK arrylist inset time--->503 JDK linkedList inset time---->11 |
多次测试,发现我们写的ArrayList在插入方法的效率都已经超过JDK了,而且也接近LinkedLst了。撒花!!!
线性表 及Java实现 顺序表、链表、栈、队列的更多相关文章
- java实现顺序表、链表、栈 (x)->{持续更新}
1.java实现节点 /** * 节点 * @luminous-xin * @param <T> */ public class Node<T> { T data; Node& ...
- Java实现顺序表
利用顺序存储结构表示的顺序表称为顺序表. 它用一组连续的地址存储单元一次存放线性表中的数据元素. 顺序表的实现是数据结构中最简单的一种. 由于代码中已经有详细注释,代码外不再阐述. 下次再陈上关于顺序 ...
- 数据结构——Java实现顺序表
一.分析 什么是顺序表?顺序表是指用一组地址连续的存储单元依次存储各个元素,使得在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中的线性表.一个标准的顺序表需要实现以下基本操作: 1.初始化顺序表 ...
- (java实现)顺序表-ArrayList
什么是顺序表 顺序表是在计算机内存中以数组的形式保存的线性表,是指用一组地址连续的存储单元依次存储数据元素的线性结构. 在使用顺序表存储数据前,会先申请一段连续的内存空间(即数组),然后把数组依次存入 ...
- Java数据结构——顺序表
一个线性表是由n(n≥0)个数据元素所构成的有限序列. 线性表逻辑地表示为:(a0,a1,…,an-1).其中,n为线性表的长度,n=0时为空表.i为ai在线性表中的位序号. 存储结构:1.顺序存储, ...
- Java算法 -- 顺序表
顺序表结构定义:就是按照顺序存储方式存储的线性表 1.定义一个顺序表的基本数据: static final int MAXLEN = 100; Class Student{ private Strin ...
- 查找->静态查找表->分块查找(索引顺序表)
文字描述 分块查找又称为索引顺序查找,是顺序查找的一种改进方法.在此查找算法中,除表本身外, 还需要建立一个”索引表”.索引表中包括两项内容:关键字项(其值为该字表内的最大关键字)和指针项(指示该子表 ...
- Java: 实现顺序表和单链表的快速排序
快速排序 快速排序原理 快速排序(Quick Sort)的基本思想是,通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可对这两部分记录继续进行排序,以达到 ...
- 顺序列表(栈/队列等)ADT[C++]
#include<iostream> using namespace std; //ADT template<class T> class SeqList{ public: / ...
随机推荐
- 关于DIV+CSS和XHTML+CSS的理解
WEB标准是一系列标准的集合,并不是仅“DIV+CSS”布局就可以实现.以CSS网页布局只是标准的基础之一.“DIV+CSS”布局只是一种通俗的称呼罢了.而我们学习的目标在于以XHTML建立良好的语义 ...
- C# 关闭 Excel进程
namespace ExcelTest { class DataOutput { static void Main(string[] args) ...
- javascript中=、==、===区别详解
javascript中=.==.===区别详解今天在项目开发过中发现在一个小问题.在判断n==""结果当n=0时 n==""结果也返回了true.虽然是个小问题 ...
- python 抽象类、抽象方法的实现
由于python 没有抽象类.接口的概念,所以要实现这种功能得abc.py 这个类库,具体方式如下 from abc import ABCMeta, abstractmethod #抽象类 class ...
- MyEclipse2015对Javascript自动提示的终极支持
2015通过集成Tern.js,进入了JS自动提示的最新时代 先看看具体效果吧: 点击链接会进入: 而tern.js已经支持相当多的框架: 关键这个提示不只是纯粹的js文件,对于jsp等等 ...
- SNMP 和 NetBios协议理解
一.简单网络管理协议(SNMP,Simple Network Management Protocol)构成了互联网工程工作小组(IETF,Internet Engineering Task For ...
- ado无法访问数据库问题
现象:以ADO方式访问数据库的C++程序,在一台计算机上能访问成功,在另一台计算机上却访问不成功,报告不能连接错误,并且这两台计算机都装有ado. 原因:ado版本不对 解决方案:下载KB983246 ...
- Improved logging in Objective-C
[Improved logging in Objective-C] Example of logging the current method and line number. Paste it in ...
- 时隔3年,再次折腾BlackBerry 8830!
2010年手头换得8830,之后就是好几番刷机.解SPC.倒腾各种软件..算软件注册码..那个时候记得最难弄的注册码就是crunchSMS.需要运行虚拟机来从内存地址读取注册码..不过黑莓真的很经得起 ...
- POJ1463 Strategic game (最小点覆盖 or 树dp)
题目链接:http://poj.org/problem?id=1463 给你一棵树形图,问最少多少个点覆盖所有的边. 可以用树形dp做,任选一点,自底向上回溯更新. dp[i][0] 表示不选i点 覆 ...