数据结构--数组和链表的基础知识

目录

简介

一、数组

二、链表

2.1 单向链表

2.1.1 单向链表的概念

2.2.2 单链表删除节点

2.1.3 单链表添加节点

2.2 双向链表

2.2.1 双向链表的概念

2.2.2 双链表删除节点

2.2.3 双链表添加节点

2.3 循环链表

2.4 循环双向链表的实现(Java实现)

三、总结:数组与链表的特点


简介

线性表是一种线性结构,它是具有相同类型的n(n≥0)个数据元素组成的有限序列,线性表的基本类型主要包括数组、链表、栈、队列等4种。本文先主要介绍其中的 数组和链表(单向链表、双向链表)的基础知识,并在最后给出双向链表的代码实现(Java语言实现)。


一、数组

数组是很常见的一种线性数据结构,它用一组连续的内存空间,来存储一组具有相同类型的数据。数组中元素的逻辑顺序和存储顺序都是连续的,有上界和下界,数组的元素在上下界内是连续的。例如存储10,20,30,40,50的数组的示意图如下:

数组的特点是:逻辑结构和存储结构都是连续的,并且是顺序是对应的。也是因为这两个限制,使得数组的随机访问”相对高效。同时这两个特点也带来的弊端是增删操作变得非常低效。原因是,为了保证连续性,需要做大量的数据搬移工作。

数组中稍微复杂一点的是多维数组和动态数组。多维数组本质上也是通过一维数组实现的。至于动态数组,是指数组的容量能动态增长的数组,一般都是,当当前数组的容量不够用的时候会自动创建一个新的数组(新数组的容量是当前数组的x倍,x一般是1.5,可以自己设置),然后将当前数组的数据复制到新数组中,这就实现了数组容量的自动增长。对于C语言而言,若要提供动态数组,需要手动实现;而对于C++而言,STL提供了Vector;对于Java而言,Collection集合中提供了ArrayList和Vector。


二、链表

链表与数组的区别是,它不像数组那样使用连续的内存空间,而是通过“指针”将一组零散的内存块串联起来。链表是由节点组成,每个节点除了包含当前元素的数据之外,还都包含指向下一个节点的指针,具体示意图在后面的介绍中都有。常见的链表有:单链表、双向链表和循环链表等。

2.1 单向链表

2.1.1 单向链表的概念

单向链表的示意图如下,单链表中的每个节点包含两部分:数据部分和后继节点指针部分。

单链表的特点是:节点的链接方向是单向的;相对于数组来说,单链表的的随机访问速度较慢,但是单链表删除/添加数据的效率很高

将单链表的中每一个节点看成一个整体,单链表的示意图可以表示如下:

表头的数据部分为空,表头的后继节点是"节点10"(数据为10的节点),"节点10"的后继节点是"节点20"(数据为10的节点),...,链表的最后一个节点的后继节点指针为空

2.2.2 单链表删除节点

单向链表中删除节点的基本操作过程如下:

  • 目的:删除"节点30"
  • 删除之前:"节点20" 的后继节点为"节点30",而"节点30" 的后继节点为"节点40"。
  • 删除之后:"节点20" 的后继节点为"节点40"。

2.1.3 单链表添加节点

单链表添加节点的基本操作过程如下:

  • 目的:在"节点10"与"节点20"之间添加"节点15"
  • 添加之前:"节点10" 的后继节点为"节点20"。
  • 添加之后:"节点10" 的后继节点为"节点15",而"节点15" 的后继节点为"节点20"。

2.2 双向链表

2.2.1 双向链表的概念

和单链表一样,双链表也是由节点组成,只是双向链表中每个节点由3部分组成:前驱节点指针、数据、后继节点指针。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。它不像单链表那样只支持单项遍历,它可以支持双向遍历。

在双向链表中,前继节点指针为空的是表头,后继节点指针为空的是表尾。

 

双向链表的删除节点和插入节点跟单向链表基本一致,只是在删除和插入节点时加上对前驱节点指针的修改即可。 

2.2.2 双链表删除节点

双向链表删除节点的基本操作过程:

  • 目的:删除"节点30"
  • 删除之前:"节点20"的后继节点为"节点30","节点30" 的前继节点为"节点20"。"节点30"的后继节点为"节点40","节点40" 的前继节点为"节点30"。
  • 删除之后:"节点20"的后继节点为"节点40","节点40" 的前继节点为"节点20"。

2.2.3 双链表添加节点

双向链表添加节点的基本操作过程如下:

  • 目的:在"节点10"与"节点20"之间添加"节点15"
  • 添加之前:"节点10"的后继节点为"节点20","节点20" 的前继节点为"节点10"。
  • 添加之后:"节点10"的后继节点为"节点15","节点15" 的前继节点为"节点10"。"节点15"的后继节点为"节点20","节点20" 的前继节点为"节点15"。

 

2.3 循环链表

循环链表是一种特殊的链表,它是将链表的结尾节点的后继指针指向头节点。下图所示是单链表的循环链表,同样,双链表也可以形成循环链表。

循环链表的添加与删除主要是要重点考虑结尾节点和 头结点的前继节点指针和后继节点指针问题,在这里就不详细描述了,大家可以自己画图展示一下。

2.4 循环双向链表的实现(Java实现)

/**
 * Java 实现的双向链表,双向链表包含的基本操作如下:
 * 构造双向链表
 * 获取链表中节点个数
 * 链表判空
 * 获取表头
 * 获取表尾
 * 查询第i个节点数据
 * 插入节点到指定位置
 * 删除指定位置节点
 * ...
 * 
 * 注:java自带的集合包中有实现双向链表,路径是:java.util.LinkedList
 *
 * @author mukekeheart
 * @date 2020/07/18
 */
public class DoubleLink<T> {
	
    private DNode<T> mHead;		// 表头
    private int mCount;			// 节点个数

    // 构造函数
    public DoubleLink() {
        // 创建“表头”。注意:表头没有存储数据!
        mHead = new DNode<T>(null, null, null);
        mHead.prev = mHead.next = mHead;
        // 初始化“节点个数”为0
        mCount = 0;
    }

    // 返回节点数目
    public int size() {
        return mCount;
    }

    // 返回链表是否为空
    public boolean isEmpty() {
        return mCount==0;
    }

    // 获取index位置的节点
    private DNode<T> getNode(int index) {
        if (index<0 || index>=mCount)
            throw new IndexOutOfBoundsException();

        // 指定位置index在前半部分就 正向查找
        if (index <= mCount/2) {
            DNode<T> node = mHead.next;
            for (int i=0; i<index; i++)
                node = node.next;
            return node;
        }

        // 指定位置index在后半部分就 反向查找
        DNode<T> rnode = mHead.prev;
        int rindex = mCount - index -1;
        for (int j=0; j<rindex; j++)
            rnode = rnode.prev;

        return rnode;
    }

    // 获取第index位置的节点的值
    public T get(int index) {
        return getNode(index).value;
    }

    // 获取第1个节点的值
    public T getFirst() {
        return getNode(0).value;
    }

    // 获取最后一个节点的值
    public T getLast() {
        return getNode(mCount-1).value;
    }

    // 将节点插入到第index位置之前
    public void insert(int index, T t) {
    		//如果插入的位置是最前面
        if (index==0) {
            DNode<T> node = new DNode<T>(t, mHead, mHead.next);
            mHead.next.prev = node; 
            mHead.next = node;
            mCount++;
            return ;
        }

        DNode<T> inode = getNode(index);
        DNode<T> tnode = new DNode<T>(t, inode.prev, inode);
        inode.prev.next = tnode;
        inode.prev = tnode;
        mCount++;
        return ;
    }

    // 将节点插入第一个节点处。
    public void insertFirst(T t) {
        insert(0, t);
    }

    // 将节点追加到链表的末尾
    public void appendLast(T t) {
        DNode<T> node = new DNode<T>(t, mHead.prev, mHead);
        mHead.prev.next = node;
        mHead.prev = node;
        mCount++;
    }

    // 删除index位置的节点
    public void delNode(int index) {
        DNode<T> inode = getNode(index);
        inode.prev.next = inode.next;
        inode.next.prev = inode.prev;
        inode = null;
        mCount--;
    }
}

class DNode<T> {
    public DNode<T> prev;	//前驱节点指针
    public DNode<T> next;	//后继节点指针
    public T value;		//节点数据
    
    //双向节点构造函数
    public DNode(T value, DNode prev, DNode next) {
        this.value = value;
        this.prev = prev;
        this.next = next;
    }
}

三、总结:数组与链表的特点

数组和链表由于其存储特点不一样,所以两个在不同方面表现的优缺点也有所不同。下面是一个简单的总结:

  • 数组优点: 随机访问性强,查找速度快(连续内存空间导致的);
  • 数组缺点: 插入和删除效率低 可能浪费内存 内存空间要求高,必须有足够的连续内存空间。数组大小固定,不能动态拓展
  • 链表的优点: 插入删除速度快,内存利用率高,不会浪费内存 大小没有固定,拓展很灵活。(每一个数据存储了下一个数据的地址,增删效率高)
  • 链表的缺点:不能随机查找,必须从第一个开始遍历,查找效率低
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值