顺序表和链表的简单介绍

本文介绍了线性表的基本概念,深入探讨了顺序表和链表的定义、功能实现,包括插入、删除、查找和修改操作。顺序表通过数组实现,适合查找操作,而链表插入删除更灵活但查找相对较慢。两者各有优势,适用于不同场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、线性表

1、基本概念

线性表是一组同类型数据的集合,逻辑结构为线性结构,对于任何一个非空线性表都有以下特点

  • 有且只有一个结点无前驱(头结点)
  • 有且只有一个结点无后进(尾结点)
  • 除头结点外,其他结点有且只有一个前驱
  • 除尾结点外,其他结点有且只有一个后进

注意:线性表的定义只是逻辑上是线性结构,物理上不一定有关系

二、顺序表

1、概念

顺序表的本质是一个顺序存储的线性表,是用一组地址连续的存储单元依次存储线性表的数据元素,一般情况使用数组储存,特点是逻辑上相邻的元素,其物理次序也是相邻的。顺序表一般可分为:

  • 静态顺序表:使用定长数组存储。
  • 动态顺序表:使用动态开辟的数组存储。

2、实现功能

顺序表的的功能实现比较简单,就相当于对数组的增删查改,Java集合框架中自带的类ArrayList就是一个顺序表。

(1)、插入

插入分为两种,一种是末尾插入,一种是中间插入。
因为是一个数组,所以中间元素的插入会带动其他元素的移动。
在这里插入图片描述

public void add(int pos, int data) {
        if (isFull()){
            System.out.println("顺序表已满,需要扩充");
            this.elem = Arrays.copyOf(elem,elem.length*2);
        }
        if (pos < 0||pos > this.usedSize){
            return;
        }
        for (int i = usedSize-1 ; i <= pos ; i--) {
            elem[i+1] = elem[i];
        }
        elem[pos] = data;
        usedSize++;
    }

(2)、删除

删除和插入大致上相同,也分为两种,一种是删除末尾元素,一种是删除中间元素

public void remove(int toRemove) {
        for (int i = 0; i < this.usedSize - 1; i++) {
            if (elem[i] == toRemove) {
                for (int j = i; j < this.usedSize; j++) {
                    elem[j] = elem[j + 1];
                }
                usedSize--;
                break;
            }
        }
        if (elem[usedSize - 1] == toRemove) {
            usedSize--;
            return;
        }
    }

(3)、查找

查找有两种方式,一种是给下标找值,另一种是给值找下标,因为本质是一个数组,下标可以直接找到,所以第一种方式很简单,第二种也只需要遍历数组,找到元素就可以了。

//给下标找值
public int getPos(int pos) {
        if (pos < 0||pos > this.usedSize-1){
            return -1;
        }
        return elem[pos];
    }
//给值找下标
public int search(int toFind) {
        for (int i = 0; i <this.usedSize-1 ; i++) {
            if (elem[i] == toFind){
                return i;
            }
        }
        return -1;
    }    

(4)、修改

修改其实就是在查找的基础上稍加改动,把寻找到的值做出修改就完成了。

public void setPos(int pos, int value) {
        if (pos < 0||pos > this.usedSize-1){
            return ;
        }
        elem[pos] = value;
    }

三、链表

1、概念

链表本质也是一个线性表,是一个链式存储的线性表,链表可以用一组任意的储存单元储存线性表的数据元素(这组存储单元可以是连续的也可以是不连续的),因此,为了表示每个元素和它后一个元素之间的逻辑关系,我们在存储数据的同时,还会存储下一个存储单元的地址。这两部分信息组成元素的存储映像,称为结点。它包含两个域,数据域指针域,根据指针域中指针的个数、指向,我们可以把链表分为单向链表、双向链表、循环链表等等,还有带头结点和不带头头结点的链表,Java集合框架中LikeList底层是一个双向链表,我们这里主要介绍的是单向不带头链表。
在这里插入图片描述
注意:200、400均为结点的地址,本为16进制,为画图方便,采用了简化。

单向链表的特点

  • 需要一个头指针指向头结点
  • 尾结点中指针域为null
  • 除头结点以外,其余结点的地址存放在前驱的指针域

双向链表的特点

  • 头结点中prev域为null
  • 需要一个尾指针指向尾结点

循环链表的特点

  • 尾结点的指针域不再是null,而是存放头结点的地址

2、功能实现

(1)、插入

与顺序表相比,链表的插入不需要频繁的移动元素,但是时间复杂度上却都是O(n),因为链表中地址不连续我们不能直接找到某一下标的地址,需要从头结点开始遍历链表,相当于查找的时间复杂度为O(n),实际插入的时间复杂度是O(1),查找和搬移元素相比较,肯定还是查找更快,所以尽管时间复杂度相同,但是对插入这一操作,链表的表现会优于顺序表。

插入分类:

  • 头插
  • 中间插入(包含尾插)
    //头插
	public void addfirst(int a){
        Node node = new Node(a);
        node.next = this.head;
        this.head = node;
    }
    //尾插
    public void addlest(int a) {
        Node node = new Node(a);
        Node cur = this.head;
        if (this.head == null) {
            this.head = node;
        } else {
            while (cur.next != null) {
                cur = cur.next;
            }
            cur.next = node;
        }
    }
    //中间插
    public void addIndex(int Index ,int a){
        Node node = new Node(a);
        if (Index == 0){
            node.next = this.head;
            this.head = node;
            return;
        }
        if (Index < 0 ||Index > getlence()){
            System.out.println("Index 位置不合法");
            return;
        }
        Node cur = this.head;
        while (Index-1 != 0){
            cur = cur.next;
            Index--;
        }
        node.next = cur.next;
        cur.next = node;

    }
    

(2)、删除

删除操作相比较插入操作就会复杂许多,其中主要的问题就是当你找到要删除结点时,因为是链表,所以你无法知道你的前一个结点的地址是什么,所以我们有两种方法来删除元素
方法一:记录结点法,首先判断头结点是否是要删除结点,如果不是,定义一个cur指针指向头结点的下一个结点,再定义一个前驱指针prev指向头结点,每次判断cur结点的值是否是要删除的元素,若不是则让cur和prev同时指向当前结点的下一个结点,直到找到要删除结点。
在这里插入图片描述

public void reMove(int key){
        if (this.head == null){
            return;
        }
        if(this.head.data == key){
            this.head = this.head.next;
            return;
        }
        Node cur = this.head.next;
        Node prev = head;
        while (cur != null){
            if (cur.data == key){
                prev.next = cur.next;
                cur = cur.next;
            }else {
                prev = cur;
                cur = cur.next;
            }
        }
    }

方法二:替罪法,首先查找待删元素的结点地址,然后让待删结点的下一结点的元素与该结点互换,最后删除下一结点。
在这里插入图片描述

public void reMove(int key){
        if(this.head == null){
            return;
        }
        Node cur = this.head;
        while(cur.data == key){
            Node del = cur.next;
            cur.data = del.data;
            cur.next = del.next;
        }
    }

(3)、查找

查找相对前面的操作就比较简单了,链表一般很少用到下标,所以都是给元素找地址,或者还有一种就是判断链表中是否包含这个元素,也属于查找的一种。

public boolean contains(int key){
	Node cur = this.head;
	while(cur != null){
		if(cur.data == key){
			return true;
		}
	}
	return false;
}

四、总结

顺序表和链表各有千秋,不能笼统的说谁好谁坏,要参考对应的情况,比如插入删除操作频繁的方法就需要同链表,比如查找元素频繁或者对查找有时间限制的情况就需要用到顺序表。详细的对比我会在后面再做一个总结!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值