数据结构与算法—线性表详解

前言


  • 通过前面数据结构与算法前导我么知道了数据结构的一些概念和重要性,那么我们今天总结下线性表相关的内容。当然,我用自己的理解解分享给大家。
  • 其实说实话,可能很多人依然分不清线性表,顺序表,和链表之间的区别和联系!
    • 线性表逻辑结构, 就是对外暴露数据之间的关系,不关心底层如何实现。
    • 顺序表、链表物理结构,他是实现一个结构实际物理地址上的结构。比如顺序表就是用数组实现。而链表用指针完成主要工作。不同的结构在不同的场景有不同的区别。
  • 对于java来说,大家都知道List接口类型,这就是逻辑结构,因为他就是封装了一个线性关系的一系列方法和数据。而具体的实现其实就是跟物理结构相关的内容。比如顺序表的内容存储使用数组的,然后一个get,set,add方法都要基于数组来完成,而链表是基于指针的。当我们考虑对象中的数据关系就要考虑指针的属性。指针的指向和value。

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

线性表基本架构


  • 对于一个线性表来说。不管它的具体实现方法如何,我们应该有的函数名称实现效果应该一致。你也可以感觉的到在一些结构的设计。比如List的ArraylistLinkedList。Map的HashMap和currentHashMap他们的接口api都是相同的,但是底层设计实现肯定是有区别的。
  • 所以,基于面向对象的编程思维,我们可以将线性表写成一个接口,而具体实现的顺序表和链表可以继承这个接口的方法,提高程序的可读性。
  • 还有一点比较重要的,记得初学数据结构与算法时候实现的线性表都是固定类型(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为插入的数据


  • 根据图片你就很好理解插入操作。当插入一个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。那么操作流程为

  1. node.next=pre.next如下1的操作,将插入节点后面联系起来。此时node.next和pre.next一致。
  2. pre.next=node因为我们要插入node,而node链可以替代pre自身的next。那么直接将pre指向node。那么就相当于原始链表插入了一个node。


带头节点与不带头节点


很多人搞不清什么是带头节点不带头节点。带头节点就是head节点不放数据,第0项从head后面那个开始数。而不带头节点的链表head放数据,head节点就是第0位
主要区别:

  • 带头节点和不带头节点的主要区别就在插入删除首位,尤其是首位插入。带头节点找元素需要多遍历一次因为它的第一个head节点是头节点,不存数据(可看作一列火车的火车头)。而方便的就是带头节点在首位插入更简单。因为插入第0位也是在head的后面
  • 而不带头节点的链表就需要特殊考虑首位。因为插入第0位其实是插入head的前面。假设有head,插入node。具体操作为:
    1. node.next=head;(node指向head,node这条链成我们想要的链)
    2. head=node;(很多人想不明白,其实这个时候node才是插入后最长链的首位节点,head在他的后面,而在链表中head通常表示首位节点,所以head不表示第二个节点,直接"="node节点。这样head和node都表示操作完成的链表。但是对外暴露的只有head。所以head只能指向第一个节点!)

插入尾

  • 而在插入尾部的时候,需要注意尾部的nextnull。不能和插入普通位置相比!

删除


按照index移除:delete(int index)

  • 找到该index的节点node。node.next=node.next.nex

按照尾部移除(拓展):deleteEnd()

  • 这个方法我没有写,但是我给大家讲一下,按照尾部删除的思想就是:

    1. 声明一个node为head。
    2. node.next!=nullnode=node.next 指向下一个
    3. node.next==null时候。说明这个节点时最后一个。你可以node=null。这个这个node的前驱pre的next就是null。这个节点就被删除了。

头部删除(带头节点):

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

头部删除(不带头节点)

  • 我们知道不带头节点的第一个就是存货真价实的元素的。不带头节点删除也很简单。直接将head移到第二位就行了。即:head=head.next

其他

  • 对于其他操作,主要时结合查找。而单链表的查找时从head开始。然后另一个节点team=headhead.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后 回复 数据结构 即可获得精心准备的数据结构和算法资料一份!超丰富。

原文地址:https://www.cnblogs.com/bigsai/p/11345180.html

时间: 2024-10-13 15:53:33

数据结构与算法—线性表详解的相关文章

数据结构与算法-线性表

近期在学习数据结构,反反复复已经看过几遍了,也做了一些练习题,但总感觉不记录一下,思路就不是很清晰,所以,从今天开始总结这段时间对数据结构的学习. 无论学习什么,基础知识都是最总要的,数据结构也不例外.线性表就是数据结构的基础,很多常见的数据结构都是基于线性表来实现的. 那么,什么是线性表呢?官方的定义是: 零个或多个数据元素的有限序列 可以从两个方面来理解线性表,首先它是一个序列,也就是其中的元素有先后顺序,其次是有限的,对于无线数列,也只能存在于理论数学中. 说了这么多,小结一下: 1)线性

数据结构与算法——线性表顺序存储结构

今天总结一下数据结构中的线性表中的顺序存储结构,这是一种比较简单的结构. 首先,看一下什么是线性表. 什么是线性表? 线性表是一种简单的数据结构,数据元素之间是一对一的关系,即除了第一个和最后一个元素外其余元素都是首尾相接的.元素的个数称为线性表的长度,长度为零则表示线性表为空. 什么是线性表的循序存储结构? 线性表中的顺序存储结构就是把线性表中的元素按逻辑次序依次存放在一组地址连续的存储空间中,也把这种线性表称为顺序表.根据顺序表的特点,通常是用数组来存储元素的. 下面就看具体的实现(C++)

数据结构与算法-线性表的实现(1)

今天来说说线性表的实现 这里以List作为例子 package com.ray.testobject; public class List { private int length; private Man[] array; public int getLength() { return length; } public void setLength(int length) { this.length = length; } public Man[] getArray() { return ar

数据结构与算法-线性表顺序存储结构删除操作的实现

这一章节我们来看一下线性表顺序存储结构删除操作的简单实现 package com.ray.testobject; public class Test { private Object[] list; public Object[] getList() { return list; } /** * 初始化list * * @param num * 元素个数 */ private void iniList(int num) { list = new Object[num]; for (int i =

数据结构与算法--线性表系列(循环链表、双向链表)

hello,everybody,今天我们来学习线性表的最后两种形式,循环链表.双向链表.这两种链表,是链式存储结构的不同形式.书归正传,我们先来看看循环链表吧. 大家思考一个问题,我们把线性表各个元素比作下图的路线图上的城市: 我们的线性表各个结点的指针,都是指向唯一的后继结点,线性表的终端结点的指针为空.这样的话,如果我们在南京,我们需要先访问南京右j边的城市,再访问南京左边的城市.根据线性表的结构,我们只能返回上海,从上海依次访问到北京.因为我们的终端结点的指针为空,如果直接访问南京右边的城

数据结构与算法-线性表的定义与特点

1.线性表概念 线性表是由零个或者多个数据元素组成的有序的序列. 图示: 2.特点 2.1 有序 我们可以从上图看见,线性表里面的元素是一个挨着一个顺序排下去的,就像平常小朋友排队等放学的样子 2.2 允许零元素,也就是空表 2.3 第一个元素有且仅有一个后继,最后一个元素有且仅有一个前驱,其他元素有且仅有一个前驱以及有且仅有一个后继 我们可以从上图看见,线性表里面第一个元素a1,他没有前驱,只有一个a2的后继,最后一个元素an,只有一个前驱a(n-1),没有对应的后继,其他某个元素ai,它有且

数据结构与算法-线性表之双向链表

参考博客: http://www.cnblogs.com/skywang12345/p/3561803.html https://blog.csdn.net/howlaa/article/details/38513235 1.概述 线性表是一种线性结构,由数组.单项链表和双向链表组成,这里讲讨论双向链表的理论知识和实现代码. 双向链表和单项链表类似,双向链表由两个指针作为指针域,分别指向前驱节点和后驱节点.可以从任何节点开始向前或者向后删除.增加.查找节点. 双链表的示意图如下: 在插入节点的时

数据结构与算法——线性表

1.概念 线性表可以看做一种抽象的概念,也可以作为一种抽象数据类型,一个线性表是某类元素的集合,还记录着元素之间的一种顺序关系.相当于一个抽象类,只做定义. 2.具体实现 1.顺序表 顺序表的基本实现方式非常简单:表中元素顺序存放在一片足够大的连续储存区间里,首元素存入储存区的开始位置,其余元素依次顺序存放,元素之间的逻辑关系通过元素在储存区里的物理位置表示(隐式表示元素之间的关系) 顺序表在内存中的布局方式: 1.顺序表基本操作的实现 创建和访问操作 创建空表时,需要分配一块元素储存,记录表的

java数据结构和算法------线性表(顺序表结构)

1 package iYou.neugle.list; 2 3 public class MySeqList<T> { 4 private int initMaxSize = 10; 5 private T[] list; 6 private int listLen = 0; 7 8 public MySeqList() { 9 this.Init(); 10 } 11 12 public MySeqList(int size) { 13 this.initMaxSize = size; 14