数据结构基础-链表

什么是链表

链表是用来存储数据集合的数据结构。有如下属性:

  • 相邻元素通过指针连接
  • 最后一个的后继指针为NULL
  • 链表长度可以增加和缩小
  • 空间按需分配,直至内存耗尽,但是存储指针会相对数组耗费一些额外空间
linkedlist

链表抽象数据结构

主要操作:

  • 添加元素,
  • 删除元素(移除并返回链表中指定位置的元素)

辅助操作:

  • 获取元素个数
  • 查询(寻找从链表表头开始的第n个结点),
  • 清空元素

这里给出插入和删除的java代码示例:

/* time:O(n) space:O(1) */
public ListNode insertLinkedList(ListNode headNode, ListNode nodeToInsert, int positon){
        if (headNode == null) {
            return nodeToInsert;
        }
        if (nodeToInsert == null) {
            return headNode;
        }
        //这里需要效验position是否在有效范围
        if (position == 0) {
            nodeToInsert.setNext(headNode);
            return nodeToInsert;
        }else {
            ListNode previousNode = headNode;
            for (int i = 1; i < positon; i++) {
                previousNode = previousNode.getNext();
            }
            nodeToInsert.setNext(previousNode.getNext());
            previousNode.setNext(nodeToInsert);
        }
        return headNode;
    }
    
public ListNode deleteNodeFromLinkedList(ListNode headNode, int position){
        if (position == 1) {
            ListNode currentNode = headNode.getNext();
            headNode = null;
            return currentNode;
        }else {
            ListNode previousNode = headNode;
            for (int i = 1; i < position; i++) {
                previousNode = previousNode.getNext();
            }
            ListNode curNode = previousNode.getNext();
            previousNode.setNext(curNode.getNext());
        }
        return headNode;
    }

为什么用链表

数组一样能用来存储数据集合,那为什么用多种数据结构来做一样的事情。因为后来发现数组在处理一些情况下的弊端,所以开始分使用情景用不同的工具干同样的事情。
先说说数组在一些情况下的缺点,

  • 大小是固定的,需要分配一块连续的空间块,就造成有时候无法分配能存储整个数组的内存空间(当数组规模太大时),(当然动态数组通过到达数组最大长度后再申请更大容量数组来加入新元素)大空间没有足量的元素也会造成空间的浪费。
  • 基于位置的插入操作实现复杂,考虑最糟的一种情况,插入到数组开始的位置,就需要移动原有数组的每一个元素。

对数组,链表最大的有点在于在任何位置插入元素的时间开销仅为O(1)。然而查找某个元素的开销为O(n)。

链表、数组和动态数组的对比

linedlist_compare

双向链表与循环链表

双向链表是单项链表的拓展,就是加入指向前一个结点的指针,用NULL表示指针的结束。循环指针就是头指针指向尾结点地址,形成了一个贪吃蛇的形状,没有NULL指针,需要注意无限循环遍历,因为每一个结点都有后继结点。

eg: 用链表实现栈

public class ListNodeStack{
    private ListNode headNode;
    public ListNodeStack(){
        this.headNode = new ListNode(null);
    }
    public void push(int data){
        if(headNode == null){
            headNode = new ListNode(data);
        }else if(headNode.getData() == null){
            headNode.setData(data);
        }else {
            ListNode node = new ListNode(data);
            node.setNext(headNode);
            headNode = node;
        }
    }
    public void pop(){
        if(isEmpty()){
            throw new EmptyStackException("Stack empty");
        }else {
            int data = headNode.getData();
            headNode = headNode.getNext();
            return data;
        }
    }
    public boolean isEmpty(){
        return null == headNode;
    }
    public void deleteStack(){
        headNode = null;
    }
}

eg1:知道链表倒数第n个结点

/* space:O(1) , time:O(n)*/
    public ListNode nthNodeFromEnd(ListNode head, int nthNode){
        ListNode fast = head, slow = head;
        for (int i = 1; i < nthNode; i++) {
            if (fast != null) {
                fast = fast.getNext();
            }
        }
        while(fast != null) {
            fast = fast.getNext();
            slow = slow.getNext();
        }
        return slow;
    }

eg2: 判定给定的链表是以NULL结尾,还是形成了一个环。
解决方法是经典的快慢指针法也叫Floyd环判定算法:试想一下乌龟和兔子在同一个轨道上赛跑。如果它们在同一个环上赛跑,那么跑得快的兔子将赶上跑得慢的乌龟,并在某一点相遇。


image

image
public boolean doesLinkedListContainerLoop(ListNode head){
        if (head == null) {
            return false;
        }
        LsitNode fastPtr = head, slowPtr = head;
        while (fastPtr != null && fastPtr.getNext() != ll) {
            fastPtr = fastPtr.getNext().getNext();
            slowPtr = slowPtr.getNext();
            if (fastPtr == slowPtr) {
                return true;
            }
        }
        return false;
    }

引申问题探究:
在前面的问题的求解方法中,一旦乌龟和兔子相遇就代表着链表中含有环。然后,乌龟从表头开始移动,而兔子从相遇的位置开始移动,乌龟和兔子每次都移动一个节点,当乌龟和兔子再次相遇,他们一定相遇在环的起始结点。WHY?
室友的帮助我理解加了下,下面是解答:题目基础是这个两个都从起点出发,在环中某个结点相遇。所以,假设环的结点个数或者长度为L,而链表头结点到环的结点的距离为m;假设第一次相遇距离环的起点为k;开始的环境是兔子每移动两步,乌龟移动一步,则从起点开始,兔子和乌龟开始出发,那么第一次相遇的时候,由于时间相同,乌龟移动S,兔子移动2S。而S = m + A * L + k (A为自然数);对应的2S = m + B * L + k (B为自然数);两者相减可得:S = (B - A) * L,由此可得两者相遇各自走过的路是相遇的整数倍。
现在兔子在第一次相遇的k处,也就是2S(S = C * L L为自然数),乌龟在链表的起点,兔子走一步乌龟也走一步,所以走m步是2S+m也就是环的起点,乌龟走m步就也是环的起点,so。

eg3:逆置单向列表

public ListNode reverseListUsingRecursion(ListNode head){
        if (head == null || head.getNext() == null) {
            return head;
        }
        ListNode newHead = reverseListUsingRecursion(head.getNext());
        head.getNext().setNext(head);
        head.setNext(null);
        return newHead;
    }

迭代版本

public ListNode reverseList(ListNode head){
        ListNode tmp = null, nextNode = null;
        while (head != null){
            nextNode = head.getNext();
            head.setNext(tmp);
            tmp = head;
            head = nextNode;
        }
        return tmp;
    }

eg4: 逆置链表,两个为单位进行逆置,eg:1->2->3->4->x => 2->1->4->3->x

//space:o(n), time:O(n)
public ListNode reversePairRecursive(ListNode head) {
        ListNode temp;
        if (head == null || head.getNext() == null) {
            return head;
        }
        temp = head.getNext();
        head.setNext(temp.getNext());
        temp.setNext(head);
        head = temp;
        head.getNext().setNext(reversePairRecursive(head.getNext().getNext()));
        return head;
    }

迭代版本代码:

/* space:o(1), time:O(n) */
    public ListNode reversePairIterative(ListNode head) {
        ListNode temp1 = null;
        ListNode temp2 = null;
        while (head != null && head.getNext() != null) {
            if (temp1 != null) {
                temp1.next().setNext(head.getNext());
            }
            temp1 = head.getNext();
            head.setNext(temp1.getNext());
            temp1.setNext(head);
            if (temp2 == null) {
                temp2 = temp1;
            }
            head = head.getNext();
        }
        return temp2;
    }

eg5: 逆置链表,k个为单位进行逆置,eg:1 2 3 4 5 6 7 8 9 10 , k=3: 3 2 1 6 5 4 9 8 7 10

public ListNode getKPlusOneThNode(int k, ListNode head) {
        ListNode kNode = head;
        if (head == null) {
            return head;
        }
        for (int i = 1; kNode != null && (i <= k); i++, kNode = kNode.getNext()) {
            if (k == i) {
                return kNode;
            }
        }
        return head.getNext();
    }

public boolean hasKNode(ListNode head, int k) {
        if (head == null) {
            return head;
        }
        for (int i = 1; head != null && (i <= k); i++, head = head.getNext()) {
            if (i == k) {
                return true;
            }
        }
        return false;
    }

public ListNode reverseBolckOf(ListNode head, int k) {
        ListNode newHead, curNode, temp, next;
        curNode = head;
        if (k ==0 || k == 1) {
            return head;
        }
        if (hasKNode(head, k)) {
            newHead = getKPlusOneThNode(k, head);
        }else {
            newHead = head;
        }
        while (curNode != null && hasKNode(curNode, k)) {
            temp = getKPlusOneThNode(k, curNode);
            int i = 0;
            while (i < k) {
                next = curNode.getNext();
                curNode.setNext(temp);
                temp = curNode;
                curNode = next;
                i++;
            }
        }
        return newHead;
    }

看动画理解「链表」实现LRU缓存淘汰算法

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

推荐阅读更多精彩内容