线性表

线性表:零个或多个元素的有限序列;

线性表.jpg

线性表的顺序存储结构

线性表的顺序存储结构:用一段地址连续的存储单元依次存储线性表的数据元素。

package dataStructure;
//线性表顺序存储结构
public class SeqList<T>{
    public static int MAXSIZE = 20; //存储空间初始分配量
    private int length; //线性表当前长度
    private T[] data; //数组存储数据元素
    
    @SuppressWarnings("unchecked")
![Uploading 线性表_425942.jpg . . .]
    public SeqList(){
        data = (T[]) new Object[MAXSIZE];
    }
    //获取第i个元素
    public T getElem(int i){
        if(i<1 || i>getLength()){
            return null;
        }else{
            return data[i-1];
        }
    }
    
    //在位置i插入元素e
    public boolean ListInsert(int i,T e){
        //若顺序表已满
        if(getLength() == MAXSIZE){
            return false;
        }
        //i不在范围内
        if(i<1 || i>getLength()+1){
            return false;
        }else{
            //插入位置不在表尾
            if(i<getLength()){
                for(int j= getLength()-1;j>=i-1;j--){
                    data[j+1] = data[j];
                }
            }
            data[i-1] = e;
            setLength(getLength() + 1);
            return true;
        }
    }
    
    //删除第i个元素
    public T ListDelete(int i){
        //若表为空
        if(getLength() == 0){
            return null;
        }
        if(i<1 || i>getLength()){
            return null;
        }else{
            T e = data[i-1];
            //若不是最后一个
            if(i<getLength()){
                for(int j=i-1;j<getLength();j++){
                    data[j] = data[j+1];
                }
            }
            setLength(getLength() - 1);
            return e;
        }
    }
    public int getLength() {
        return length;
    }
    public void setLength(int length) {
        this.length = length;
    }
}

测试代码:

package dataStructure;  
  
import java.util.Random;  
  
public class SeqListTest {  
    final int MAX = 25;  
    Random r = new Random();  
    SeqList<Integer> seqList;  
      
    public SeqListTest() {  
        initSeqList();  
    }  
      
    //创建一个线性表顺序存储结构  
    public void initSeqList() {  
  
        seqList = new SeqList<Integer>();  
//      int length = (int) Math.random();   //只能产生0.0 - 1.0之间的double随机数  
        int length = Math.abs(r.nextInt(MAX));  //使用Random随机产生一个25左右的值,使用Math.abs()函数来取绝对值    
        System.out.println("产生的数组长度为 :" + length);  
          
        if(length > SeqList.MAXSIZE) {  
            System.out.println("该长度不合法");  
        }  
          
        for (int i = 1; i <= length; i++) {  //为生成的数组赋值,同时也测试了插入值的方法  
            int j =r.nextInt(MAX);  
            System.out.print(j + " ");  
              
            if(!seqList.ListInsert(i, j)) {  
                System.exit(0);   
            }  
        }  
        System.out.println("\n原始数组是 :");  
        display(seqList);  
    }  
      
    //测试删除方法  
    public void deleteElem() {  
        int i = r.nextInt(MAX);  
        System.out.println("\n\n删除的位置是:" + i);  
        Integer deleteNumber = seqList.ListDelete(i);  
          
        if( deleteNumber == null) {  
            System.exit(0);  
        } else {  
            System.out.println("删除的元素是 : " + deleteNumber);  
            System.out.println("删除元素后数组是 :");  
            display(seqList);  
        }  
    }  
      
    //测试随机插入方法  
    public void insertByRandom() {  
        int i = r.nextInt(MAX);  
        System.out.println("\n\n随机插入位置是 :" + i);  
        int elem = r.nextInt(MAX);  
        System.out.println("随机插入数据是 :" + elem);  
        seqList.ListInsert(i, elem);  
        System.out.println("随机插入数据后数组是 :");  
        display(seqList);  
    }  
      
    //数据展示  
    public  void display(SeqList seqList) {  
        for (int i = 1; i <= seqList.getLength(); i++) {  
              
            if(seqList.getElem(i) != null) {  
                System.out.print(seqList.getElem(i) + " ");  
            }  
              
        }  
        System.out.println("数组的长度为 :" + seqList.getLength());  
    }  
      
    //获取元素  
    public void getElem() {  
        int i = r.nextInt(MAX);  
        System.out.println("\n获取位置为 :" + i);  
        System.out.println("获取到的元素为 : " + seqList.getElem(i));  
          
          
    }  
      
    public static void main(String[] args) {  
        SeqListTest s = new SeqListTest();  
        s.insertByRandom();  
        s.deleteElem();  
        s.getElem();  
    }  
} 

顺序存储优点:
1.无需为表示表中元素之间的逻辑关系而增加额外的存储空间。
2.对于存,读数据时时间复杂度都是O(1)。

顺序存储缺点:
1.插入和删除操作需要移动大量元素,时间复杂度为O(n)。
2.线性表长度变化较大时,无法确定存储空间的容量。

线性表的链式存储结构(单链表)

线性表的链式存储结构:用一组任意的存储单元存储线性表的数据元素。存储单元可以是连续的也可以是不连续的。

  • 头指针:若有头结点,则指向头结点;否则是指向第一个结点。无论链表是否为空,头指针都不为空。
  • 头结点:为了操作的方便而设立,在第一个元素之前。头结点的数据域不存储任何信息,指针指向第一个结点。若线性表为空,头结点的指针域为空。
  • 最后一个结点指针为null
package dataStructure;
//链表中的结点node
public class Node<T>{
    private T data; // 数据域
    private Node<T> next;//指针域
    public T getData() {
        return data;
    }
    public void setData(T data) {
        this.data = data;
    }
    public Node<T> getNext() {
        return next;
    }
    public void setNext(Node<T> next) {
        this.next = next;
    }
}
package dataStructure;

public class LinkList<T>{
    private Node<T> head;//头结点
    private int length;//链表的长度
    
    public LinkList(Node<T> head) {  
        this.head = head;  
    }  
    
    //获取第i个元素
    public T getElem(int i){
        int j = 1;
        Node<T> n = head.getNext();//第一个元素
        //得到第i个结点
        while(n!=null && j<i){
            n = n.getNext();
            j++;
        }
        if(n == null || j>i){
            return null;
        }else{
            return n.getData();
        }
        
    }
    
    //在位置i之后插入e
    public boolean ListInsert(int i,T e){
        int j = 1;
        Node<T> n = head;
        //若为第一次插入
        if(head == null && i==1){
            head = new Node<T>();
            head.setData(null);
            Node<T> first = new Node<T>();
            first.setData(e);
            head.setNext(first);
            length++;
            return true;
        }else{
            //得到第i个结点
            while(n!=null && j<i){
                n = n.getNext();
                j++;
            }
            if(n == null || j>i){
                return false;
            }else{
                Node<T> s = new Node<T>();
                s.setData(e);
                s.setNext(n.getNext());
                n.setNext(s);
                length++;
                return true;
            }
        }
        
    }
    
    //删除位置i
    public T ListDelete(int i){
        int j = 1;
        Node<T> n = head.getNext();
        //得到第i-1个结点
        while(n!=null && j<i-1){
            n = n.getNext();
            j++;
        }
        if(n == null || j>i-1){
            return null;
        }else{
            T e = n.getNext().getData();
            n.setNext(n.getNext().getNext());
            length--;
            return e;
        }
        
    }
    
    public Node<T> getHead() {
        return head;
    }
    public void setHead(Node<T> head) {
        this.head = head;
    }
    public int getLength() {
        return length;
    }
    public void setLength(int length) {
        this.length = length;
    }
}

测试代码:

package dataStructure;

import java.util.Random;

public class LinkListTest {
    final int MAX = 25;
    Random r = new Random();
    LinkList<Integer> linkList;
    
    public LinkListTest() {
        initSeqList();
    }
    
    //创建一个线性表顺序存储结构
    public void initSeqList() {
        linkList = new LinkList<Integer>(null);
        int length = Math.abs(r.nextInt(MAX));  //使用Random随机产生一个25左右的值,使用Math.abs()函数来取绝对值  
        System.out.println("产生的链表长度为 :" + length);
        
        for (int i = 1; i <= length; i++) { //为生成的链表赋值,同时也测试了插入值的方法
            int j =r.nextInt(MAX);
            System.out.print(j + " ");
            
            if(!linkList.ListInsert(i, j)) {
                System.exit(0); 
            }
        }
        System.out.println("\n原始链表是 :");
        display(linkList);
    }
    
    //测试删除方法
    public void deleteElem() {
        int i = r.nextInt(MAX);
        System.out.println("\n\n删除的位置是:" + i);
        Integer deleteNumber = linkList.ListDelete(i);
        
        if( deleteNumber == null) {
            System.exit(0);
        } else {
            System.out.println("删除的元素是 : " + deleteNumber);
            System.out.println("删除元素后链表是 :");
            display(linkList);
        }
    }
    
    //测试随机插入方法
    public void insertByRandom() {
        int i = r.nextInt(MAX);
        System.out.println("\n\n随机插入位置是 :" + i);
        int elem = r.nextInt(MAX);
        System.out.println("随机插入数据是 :" + elem);
        linkList.ListInsert(i, elem);
        System.out.println("随机插入数据后链表是 :");
        display(linkList);
    }
    
    //数据展示
    public  void display(LinkList<Integer> linkList) {
        Node<Integer> node = linkList.getHead();
        while(node != null) {
            System.out.print(node.getData() + " ");
            node = node.getNext();
        }
        System.out.println("链表的长度为 :" + linkList.getLength());
    }
    
    //获取元素
    public void getElem() {
        int i = r.nextInt(MAX);
        System.out.println("\n获取位置为 :" + i);
        System.out.println("获取到的元素为 : " + linkList.getElem(i));
        
        
    }
    
    public static void main(String[] args) {
        LinkListTest s = new LinkListTest();
        s.insertByRandom();
        s.deleteElem();
        s.getElem();
    }
}

虽然已上所有链表的操作时间复杂度都为O(n),但如果在第i个位置插入10个结点这种操作,对于顺序存储结构是每次插入都要移动n-i个点,每次都是O(n)。但对于链式存储结构,只要在第一次通过O(n)找到位置i,那么接下来插如的操作时间复杂度都是O(1)。
因此,对于插入或删除数据越频繁的操作,单链表的效率优势越明显。

  • 单链表的整表创建
package dataStructure;

public class CreateLinkList {
    // 头插法创建长度为n的链表
    public static LinkList<Integer> createListHead(int n) {
        Node<Integer> head = new Node<Integer>();
        LinkList<Integer> list = new LinkList<Integer>(head);
        int j = 1;
        // 将新结点插入到头结点与前一新结点之间
        while (j <= n) {
            Node<Integer> p = new Node<Integer>();
            p.setData((int) (Math.random() * 25));
            System.out.println(p.getData());
            p.setNext(head.getNext());
            head.setNext(p);
            j++;
        }
        return list;

    }

    // 尾插法创建长度为n的链表
    public static LinkList<Integer> createListTail(int n) {
        Node<Integer> head = new Node<Integer>();
        LinkList<Integer> list = new LinkList<Integer>(head);
        int j = 1;
        // 将结点插入到最后
        while (j <= n) {
            Node<Integer> p = new Node<Integer>();
            p.setData((int) (Math.random() * 25));
            System.out.println(p.getData());
            head.setNext(p);
            head = p;
            j++;
        }
        return list;
    }

    // 数据展示
    public static void display(LinkList<Integer> linkList) {
        Node<Integer> node = linkList.getHead();
        while (node != null) {
            System.out.print(node.getData() + " ");
            node = node.getNext();
        }
    }

    public static void main(String args[]) {
        display(createListHead(7));
    }
}
  • 单链表的整表删除
//整表删除
    public void clearList(){
        Node<T> p = head;
        Node<T> q = new Node<T>();
        while(q != null){
            q = p.getNext();
            if(q!=null){
                p.setNext(q.getNext());
                length--;
            }
            
        }
    }

比较两种存储方式

Paste_Image.png
  • 若线性表要频繁查找,很少进行插入和删除,宜使用顺序存储结构。反之宜用单链表结构。
  • 当线性表中的元素个数变动较大或根本不知道有多大时,最好使用单链表。而如果事先知道线性表的大致长度,则考虑使用顺序结构。

静态链表

静态链表:用数组描述的链表。
每一个数组的元素有两个数据域:数据域与指针域(游标)。

package dataStructure.StaticLinkList;

public class Node<T>{
    private T data;//数据域
    private int cur;//指针域
    public T getData() {
        return data;
    }
    public void setData(T data) {
        this.data = data;
    }
    public int getCur() {
        return cur;
    }
    public void setCur(int cur) {
        this.cur = cur;
    }
}

一般数组的第一个元素和最后一个元素会被作为特殊元素处理,不存数据。
通常把未被使用的数据元素成为备用链表。

  • 数组的第一个元素的cur用来存放备用链表的第一个结点的下标。(即第一个没有数据的下标)
  • 数组的最后一个元素的cur用来存放第一个有数值的元素的下标,相当于单链表中的头结点作用。
package dataStructure.StaticLinkList;

@SuppressWarnings("unchecked")
public class StaticLinkList<T> {
    public static int MAXSIZE = 20;
    private Node<T>[] list;
    private int length;
    
    public StaticLinkList(){
        this.list = new Node[MAXSIZE];
    }
    // 初始化链表
    public void initList() {
        for (int i = 0; i < MAXSIZE; i++) {
            Node<T> n = new Node<T>();
            n.setCur(i+1);
            list[i] = n;
        }
        
        // 目前静态链表为空,因此最后一个元素的cur为0
        list[MAXSIZE-1].setCur(0);
    }

    // 在第i个元素位置插入e
    public boolean ListInsert(int i, T e) {
        //创建链表
        if((length == 0 && i ==1)|| i == length+1){
            list[i].setData(e);
            list[MAXSIZE-1].setCur(1);
            length++;
            return true;
        }
        if (i < 1 || i > length) {
            return false;
        }
        // 首先取得备用链表第一个结点的下标:
        int cur = list[0].getCur();
        // 同时改变第一个元素的cur,因为刚取出来的cur要被使用了,不再是备用链表的第一个结点了
        list[0].setCur(list[cur].getCur());

        list[cur].setData(e);

        int k = MAXSIZE-1;
        // 此时取出来的k为第i-1个元素的下标
        for (int j = 1; j <= i - 1; j++) {
            k = list[k].getCur();
        }
        // 第i个元素的下标
        int insert = list[k].getCur();
        list[k].setCur(cur);
        list[cur].setCur(insert);
        length++;
        return true;
    }

    // 删除第i个元素
    public T ListDelete(int i) {
        if (i < 1 || i > length) {
            return null;
        }
        int k = MAXSIZE-1;
        // 此时取出来的k为第i-1个元素的下标
        for (int j = 0; j < i - 1; j++) {
            k = list[k].getCur();
        }
        //第i个元素下标:
        int del = list[k].getCur();
        T e = list[del].getData();
        list[k].setCur(list[del].getCur());
        //更改第一个元素的cur与要删除元素的cur,因为要删除的元素变成了备用链表的第一个元素
        list[del].setCur(list[0].getCur());
        list[0].setCur(del);
        length--;
        return e;
        
    }

    //展示数据
    public void display(){
        int k = list[MAXSIZE-1].getCur();
        for(int i=1;i<=length;i++){
            T e = list[k].getData();
            k = list[k].getCur();
            System.out.print(e + " ");
        }
        System.out.println("链表的长度为 :" + getLength());
    }
    public int getLength() {
        return length;
    }

    public void setLength(int length) {
        this.length = length;
    }

    public Node<T>[] getList() {
        return list;
    }

    public void setList(Node<T>[] list) {
        this.list = list;
    }

}

测试代码:

package dataStructure.StaticLinkList;

import java.util.Random;

public class StaticLinkListTest {
    StaticLinkList<Integer> sllist = new StaticLinkList<Integer>();

    // 用随机数初始化一个长度为n的静态链表
    public void init(int n) {
        sllist.initList();
        for (int i = 1; i <= n; i++) {
            sllist.ListInsert(i, (int) (Math.random() * 25));
        }
        display(sllist);
    }

    // 测试删除方法
    public void deleteElem() {
        int i = (int) (Math.random() * sllist.getLength());
        System.out.println("\n\n删除的位置是:" + i);
        Integer deleteNumber = sllist.ListDelete(i);

        if (deleteNumber == null) {
            System.exit(0);
        } else {
            System.out.println("删除的元素是 : " + deleteNumber);
            System.out.println("删除元素后链表是 :");
            display(sllist);
        }
    }

    // 测试随机插入方法
    public void insertByRandom() {
        int i = (int) (Math.random() * sllist.getLength());
        System.out.println("\n\n随机插入位置是 :" + i);
        int elem = (int) (Math.random() * 20);
        System.out.println("随机插入数据是 :" + elem);
        sllist.ListInsert(i, elem);
        System.out.println("随机插入数据后链表是 :");
        display(sllist);
    }

    // 展示list
    public void display(StaticLinkList<Integer> sslist) {
        sslist.display();
    }

    public static void main(String[] args) {
        StaticLinkListTest s = new StaticLinkListTest();
        s.init(8);
        s.deleteElem();
        s.insertByRandom();
    }

}
  • 静态链表优点:
    在插入和删除时,只需改变游标而无需移动元素。
  • 静态链表缺点:
    没有解决连续存储分配内存空间难以确定的问题;
    失去了顺序存储结构随机存取的特性。

循环链表

将单链表中的终端节点的指针由空指针改为指向头结点,就使得整个单链表形成一个环,这种头尾相接的单链表为循环链表。

循环链表解决了从任何一个结点出发从而遍历到所有结点的问题。

循环链表与单链表的主要差异:循环的判断条件,单链表是判断p.next是否为空,循环列表则判断p.next是否为头结点。

在单链表中,在访问尾结点时需要O(n)的时间。但如果在循环链表中不使用头指针,而是使用尾指针rear,则访问第一个结点和最后一个结点都是O(1)。

双向链表

在单链表的每个结点中,都有一个前驱指针指向其前驱结点。所以双向链表中的结点都有两个指针域,一个指向后继,一个指向前驱。

双向链表使得之前查前驱结点的时间复杂度由O(n)变为O(1)。

但双向链表在插入和删除时都需更改两个指针。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • 大学的时候不好好学习,老师在讲台上讲课,自己在以为老师看不到的座位看小说,现在用到了老师讲的知识,只能自己看书查资...
    和珏猫阅读 1,427评论 1 3
  • 1.线性表的定义 线性表:零个或多个数据元素的有限序列序列:也就是说元素之间是有顺序的,若元素存在多个,则第一个元...
    e40c669177be阅读 2,014评论 6 15
  • 在上一篇文章中我们简单说了数据结构的概念和数据结构与算法的一些关系,这一篇文章的内容是关于线性表的东西,主要有线性...
    硅谷小虾米阅读 1,251评论 1 3
  • 前言 什么是线性表?线性表的两大存储结构是什么?各种存储结构是如何实现存取、插入删除等操作的?本篇主要解答了这几个...
    JonyFang阅读 1,539评论 4 17
  • 地铁 我在这里看书,听歌 人多,陌生,却安全 处在相同的空间,有着不同的时间 人群将我挤到无人发现的角落 ...
    粮食和花圈阅读 144评论 0 1