单向链表和双向链表
|字数总计:2.6k|阅读时长:11分钟|阅读量:|
链表(Linked List)介绍
链表是有序的列表,它在内存中存储如下:
- 链表是以节点的方式来存储,是链式存储
- 每个节点包含data域和指针域
- 如图:链表的各个节点不一定是连续存储
- 链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定
带头结点的单向链表示意图如下:
带头结点的双向链表示意图如下:
单向链表的应用实例
使用带头结点的单向链表,实现水浒英雄排行榜对英雄人物的增删改查操作。
创建Hero对象,每一个对象就是一个节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class HeroNode { public int no; public String name; public String nickname; public HeroNode next;
public HeroNode(int no, String name, String nickname) { this.no = no; this.name = name; this.nickname = nickname; }
@Override public String toString() { return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]"; }
}
|
添加英雄时,直接添加到链表尾部
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| class SingleLinkedList { private HeroNode head = new HeroNode(0, "", "");
public HeroNode getHead() { return head; }
public void add(HeroNode heroNode) {
HeroNode temp = head; while (true) { if (temp.next == null) { break; } temp = temp.next; } temp.next = heroNode; }
public void list() { if (head.next == null) { System.out.println("链表为空"); return; } HeroNode temp = head.next; while (true) { System.out.println(temp); if (temp.next == null) { break; } temp = temp.next; } } }
|
添加英雄时,根据英雄排名添加到指定位置
如果有这个排名,则添加失败,并给出提示。如图,s为新节点,只需s->next = p->next;p->next = s;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class SingleLinkedList { public void addByOrder(HeroNode heroNode) { HeroNode temp = head; while (true) { if (temp.next == null || temp.next.no > heroNode.no) { heroNode.next = temp.next; temp.next = heroNode; break; }else if(temp.next.no == heroNode.no){ System.out.printf("准备插入的英雄的编号 %d 已经存在了, 不能加入\n", heroNode.no); } temp = temp.next; } } }
|
根据no编号修改英雄信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class SingleLinkedList { public void update(HeroNode newHeroNode) { HeroNode temp = head; while (true) { if (temp.next == null) { System.out.println("修改失败,没有找到该节点!"); break; }else if (temp.next.no == newHeroNode.no) { temp.next.name = newHeroNode.name; temp.next.nickname = newHeroNode.nickname; System.out.println("修改成功:" + temp.next); break; } temp = temp.next; } } }
|
根据no编号删除英雄信息
如图,p->next为要删除的节点,只需p->next = p->next->next;
。被删除的节点,将不会有其它引用指向,会被垃圾回收机制回收。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class SingleLinkedList { public void del(int no) { HeroNode temp = head; while(true) { if (temp.next == null) { System.out.println("找不到该节点,无法删除!"); break; }else if (temp.next.no == no) { temp.next= temp.next.next; System.out.printf("编号为 %d 的英雄删除成功!\n",no); break; } temp = temp.next; } } }
|
注意:在执行修改和删除操作时必须先判断temp.next为不为空,否则容易出现异常。
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package com.nanzx.linkedlist;
public class SingleLinkedListDemo {
public static void main(String[] args) { HeroNode hero1 = new HeroNode(1, "宋江", "及时雨"); HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟"); HeroNode hero3 = new HeroNode(3, "吴用", "智多星"); HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.addByOrder(hero1); singleLinkedList.addByOrder(hero4); singleLinkedList.addByOrder(hero2); singleLinkedList.addByOrder(hero3);
singleLinkedList.list(); } }
|
单向链表面试题
获取到单向链表节点的个数
要求:如果是带头节点的链表,不统计头节点
1 2 3 4 5 6 7 8 9 10
| public static int getLenth(HeroNode head) { int length = 0; HeroNode cur = head.next; while (cur != null) { length++; cur = cur.next; } return length; }
|
查找单向链表中的倒数第k个结点 【新浪面试题】
思路:
- 编写一个方法,接收head节点,同时接收一个index
- index 表示是倒数第index个节点
- 先把链表从头到尾遍历,得到链表的总的长度 size=getLenth
- 得到size 后,我们从链表的第一个非头结点开始遍历 (size-index)个
- 如果找到了,则返回该节点,否则返回nulll
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public static HeroNode findLastIndexNode(HeroNode head, int index) { if (head.next == null) { return null; } int size = getLenth(head); if (index <= 0 || index > size) { return null; } HeroNode cur = head.next; for (int i = 0; i < size - index; i++) { cur = cur.next; } return cur; }
|
实现单向链表的反转【腾讯面试题】
方法:头插法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public static void reversetList(HeroNode head) { if (head.next == null || head.next.next == null) { return; } HeroNode cur = head.next; HeroNode next = null; HeroNode reverseHead = new HeroNode(0, "", ""); while (cur != null) { next = cur.next; cur.next = reverseHead.next; reverseHead.next = cur; cur = next; } head.next = reverseHead.next; }
|
从尾到头打印单向链表【百度面试题】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public static void reversePrint(HeroNode head) { if (head.next == null) { return; } Stack<HeroNode> stack = new Stack<HeroNode>(); HeroNode cur = head.next; while (cur != null) { stack.push(cur); cur = cur.next; } while (stack.size() > 0) { System.out.println(stack.pop()); } }
|
双向链表的应用实例
使用带头结点的双向链表,实现水浒英雄排行榜对英雄人物的增删改查操作。
管理单向链表的缺点分析:
单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找。
单向链表不能自我删除,需要靠辅助节点,而双向链表,则可以自我删除,所以前面我们单向链表删除时节点,总是找到temp,temp是待删除节点的前一个节点。
分析双向链表的遍历,添加,修改,删除的操作思路:
遍历方法和单向链表一样,只是可以向前,也可以向后查找。
添加(默认添加到双向链表的最后):
- 先找到双向链表的最后这个节点
- temp.next=newHeroNode;
- newHeroNode.pre=temp;
修改思路和原来的单向链表一样。
删除:
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
| package com.nanzx.linkedlist;
public class DoubleLinkedListDemo {
public static void main(String[] args) { HeroNode2 hero1 = new HeroNode2(1, "宋江", "及时雨"); HeroNode2 hero2 = new HeroNode2(2, "卢俊义", "玉麒麟"); HeroNode2 hero3 = new HeroNode2(3, "吴用", "智多星"); HeroNode2 hero4 = new HeroNode2(4, "林冲", "豹子头");
DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
doubleLinkedList.add(hero1); doubleLinkedList.add(hero4); doubleLinkedList.add(hero2); doubleLinkedList.add(hero3);
doubleLinkedList.del(2);
doubleLinkedList.list(); }
}
class DoubleLinkedList { private HeroNode2 head = new HeroNode2(0, "", "");
public HeroNode2 getHead() { return head; }
public void add(HeroNode2 heroNode) { HeroNode2 temp = head; while (true) { if (temp.next == null) { break; } temp = temp.next; } temp.next = heroNode; heroNode.pre = temp; }
public void update(HeroNode2 newHeroNode) { HeroNode2 temp = head.next; while (true) { if (temp == null) { System.out.println("修改失败,没有找到该节点!"); break; } else if (temp.no == newHeroNode.no) { temp.name = newHeroNode.name; temp.nickname = newHeroNode.nickname; System.out.println("修改成功:" + temp); break; } temp = temp.next; } }
public void del(int no) { HeroNode2 temp = head.next; while (true) { if (temp == null) { System.out.println("找不到该节点,无法删除!"); break; } else if (temp.no == no) { if (temp.next != null) { temp.next.pre = temp.pre; } temp.pre.next = temp.next; System.out.printf("编号为 %d 的英雄删除成功!\n", no); break; } temp = temp.next; } }
public void list() { if (head.next == null) { System.out.println("链表为空"); return; } HeroNode2 temp = head.next; while (true) { System.out.println(temp); if (temp.next == null) { break; } temp = temp.next; } } }
class HeroNode2 { public int no; public String name; public String nickname; public HeroNode2 next; public HeroNode2 pre;
public HeroNode2(int no, String name, String nickname) { this.no = no; this.name = name; this.nickname = nickname; }
@Override public String toString() { return "HeroNode2 [no=" + no + ", name=" + name + ", nickname=" + nickname + "]"; } }
|