线性数据结构

线性数据结构就是:顺序表链表

其他的就是非线性数据结构,比如树,图等。线性数据结构的特点是,一对一,一个数据只有一个后继和一个前驱;而非线性数据结构则是一对多。

1、顺序表

顺序表是个逻辑概念,顺序表的相邻元素在物理位置上也是相邻的。通常我们采用数组实现顺序表,我们可能会把这两者混为一谈。数组是基于顺序表逻辑实现的,但在细微之处有些许差别,比如数组下标从0开始,顺序表下标从1开始。

顺序表的特点是随机存取方便,不可扩充。

image-20210917105644839

2、链表

当我们需要存放大量动态的数据时,这时就需要链表登场了。链表不需要数据存在连续的位置,它的存储结构是每个数据都有下一个结点的地址,所有的数据串成一条链,从第一个数据开始问,我们可以找到任何一个数据。相对于顺序表直接给出了每个数据的位置编号,链表的查找比较费事。

image-20210917110120388

  • 链表可以有一个空的头节点dummy,不存放数据,只用来做标识。方便数据的增删,并且对于空表方便处理。
  • 除了上面的单向链表,链表还有双向链表,循环链表,十字链表等变式。

单链表反转

链表反转一般有三种方法,原地反转,头插法反转,递归反转。其中递归反转最简单简洁,但是空间复杂度更高。下面分别介绍。

image-20210917111947851

1.原地反转

首先让pre的next指向cur的next;再让cur的next指向头节点的下一个结点;这时已经反转了结点,此时链表第一个结点变成了当前反转的结点;再让头节点的next指向cur;最后移动cur到下一个要反转的结点。重复上述步骤,直到cur为null。此时头节点也指向最后一个节点了。

带头节点的链表:

static LinkNode reverse1(LinkNode l){
    if(l.next == null || l.next.next == null){
        return l;
    }
    LinkNode pre = l.next;  // pre 不动, 移动头节点
    LinkNode cur = pre.next;
    while(cur != null){
        pre.next = cur.next;
        cur.next = l.next;
        l.next = cur;
        cur = pre.next;
    }
    return l;
}

不带头结点的链表:

一样的做法一个指向pre,一个指向cur。而且比有头节点的更加简单。

public LinkNode reverse2(LinkNode l){
    LinkNode pre = l;
    LinkNode cur = pre.next;
    while(cur != null){
        LinkNode next = cur.next;
        cur.next = pre;
        pre = cur;
        cur = next;
    }
    l.next = null;  // 记得将最开始的头节点指向null,否则形成环路
    return pre;
}

2.头插法

新建一个链表,遍历原链表,用头插法插入到新链表中。

值得注意的是,在遍历开始前,还要断开第一个结点和第二个结点之间的链。初始化工作做完后,从第二个结点开始遍历链表,依次插入新链表中,即实现反转。

image-20210917110559235

带头节点的链表:

static LinkNode reverse3(LinkNode l){
    if(l.next == null || l.next.next == null){
        return l;
    }
    LinkNode cur = l.next.next;
    LinkNode next;
    l.next.next = null;
    while (cur != null){
        next = cur.next;
        cur.next = l.next;
        l.next = cur;
        cur = next;
    }
    return l;
}

不带头结点的链表:

static LinkNode reverse4(LinkNode l){
    if(l == null || l.next == null){
        return l;
    }
    LinkNode cur = l.next;
    LinkNode next;
    l.next = null;
    while (cur != null){
        next = cur.next;
        cur.next = l;
        l = cur;
        cur = next;
    }
    return l;
}

3.递归反转

直接递归得到最后一个节点,然后每层就是将这个节点的下一个节点指向这个节点,这个节点指向null,最后返回末尾节点。(不带头节点)

static LinkNode reverse5(LinkNode l){
    if(l == null || l.next == null){
        return l;
    }
    LinkNode p = reverse5(l.next);
    l.next.next = l;
    l.next = null;
    return p;
}

3、栈

栈是一种特殊的操作受限的线性数据结构,可以用用顺序表实现也可以用链表实现。在计算机中经典的应用就是函数调用或中断时,保护现场数据时使用。

因为每次都是对栈顶元素进行操作,所以一般需要一个top指针指向栈顶。

为了操作方便,和链一样,通常我们设置了一个头节点,因此top不是指向栈顶第一个元素,而是第一个元素再上一个位置。由于栈的特性,非常适合使用头插法插入数据。

  • 栈的基本操作:

    push:入栈操作

    pop:出栈操作

    top:查看栈顶元素操作

  • 实现:

    我们可以用链表实现栈,也可以用数组实现。如果是链表,非常简单,只需要一个指针指向头节点即可,链表的优点是出栈入栈非常的快。而使用数组就面临着如何选择数组大小的问题,一般来说我们应该让数组根据元素的个数动态改变大小,java中给出的方法是每次满了就扩大一倍数组大小,同时随着不断弹出元素数组大小也应该减小,策略是元素个数小于数组的1/4,那么就把数组缩小一半。

  • 应用

    • 函数调用
    • 浏览器回退前进(两个栈)
    • 运算表达式求解(双栈,数值一个栈,运算符一个栈)
    • ...

4、队列

队列也是一种线状了操作的线性数据结构。和栈不一样,它是先进的先出FIFO,在计算机中经典的应用是进程队列。因为队列类似于现实中的排队,计算机中有排队的地方通常就会有它。

队列也有多种实现方法,常见的是基于链表实现。

  • 队列的操作有:

    入队(enqueue)添加到队尾

    出队(dequeue)从队头移出

原文地址:https://www.cnblogs.com/cpcpp/p/13371542.html