数据结构与算法系列四(单链表)
有人说,数据结构与算法,计算机网络,与操作系统都一样,脱离日常开发,除了面试这辈子可能都用不到呀!
有人说,我是做业务开发的,只要熟练API,熟练框架,熟练各种中间件,写的代码不也能“飞”起来吗?
于是问题来了:为什么还要学习数据结构与算法呢?
#理由一:
面试的时候,千万不要被数据结构与算法拖了后腿
#理由二:
你真的愿意做一辈子CRUD Boy吗
#理由三:
不想写出开源框架,中间件的工程师,不是好厨子
我想好了,还是需要学习数据结构与算法。但是我有两个困惑:
1.如何着手学习呢?
2.有哪些内容要学习呢?
学习方法推荐:
#学习方法
1.从基础开始,系统化学习
2.多动手,每一种数据结构与算法,都自己用代码实现出来
3.思路更重要:理解实现思想,不要背代码
4.与日常开发结合,对应应用场景
学习内容推荐:
数据结构与算法内容比较多,我们本着实用原则,学习经典的、常用的数据结构、与常用算法
#学习内容:
1.数据结构的定义
2.算法的定义
3.复杂度分析
4.常用数据结构
数组、链表、栈、队列
散列表、二叉树、堆
跳表、图
5.常用算法
递归、排序、二分查找
搜索、哈希、贪心、分治
动态规划、字符串匹配
在上一篇【数据结构与算法系列三(数组)】中,我们知道了最基础的数据结构:数组。在这一篇中,我们再来看另外一种基础数据结构:链表。常用的链表有:单链表、双向链表、循环链表。
这一篇我们主要看:单链表
#考考你:
1.你能用自己的话描述链表吗?
2.你知道链表和数组的区别吗?
3.你知道都有哪些常用的链表吗?
链表与数组一样,都是常用的基础数据结构,它通过“指针”将一组零散的内存块串联起来使用。每一个零散的内存块称为:节点。
为了将所有节点串联起来,每个链表节点除了存储数据,还需要存储链上下一个节点的地址,我们把存储下一个节点地址的指针,称为:后继指针。
链表有两个特殊的节点:头节点、尾节点
头节点:第一个节点
尾节点:后继指针指向null的节点
如图:
上一篇我们知道数组的一个特点:需要连续的内存空间。链表与数组刚好相反,链表不需要连续的内存空间,它是通过“指针”将一组零散的内存块串联起来使用。
如图:
数组的插入、删除操作,需要向后,向前移动数据,时间复杂度是:O(n)
链表的插入、删除操作,只需要改变节点指针,不需要移动数据,时间复杂度是:O(1)
如图:
数组的内存空间是连续的,支持随机访问操作,根据下标索引访问,时间复杂度是:O(1)
链表的内存空间不连续,不支持随机访问操作,从头节点遍历访问,时间复杂度是:O(n)
/** * 链表节点:Node<E> */ class Node<E>{ private E e; private Node<E> next; public E getE() { return e; } public void setE(E e) { this.e = e; } public Node<E> getNext() { return next; } public void setNext(Node<E> next) { this.next = next; } }
package com.anan.struct.linetable; /** * 单链表实现思路: * 1.空闲一个头节点,即头节点不存储数据 * 2.这样有利于简化链表的实现 */ public class SingleLinkedList<E> { // 链表大小 private int size; public int getSize() { return size; } // 链表头节点 private Node<E> head; // 链表尾节点 private Node<E> tail; public SingleLinkedList(){ head = new Node<E>(); tail = head; size = 1; } /** * 在链表结尾插入元素 */ public boolean add(E e){ // 创建节点 Node<E> node = new Node<E>(); node.setE(e); // 改变尾节点指针,指向新节点 tail.next = node; // 设置新的尾节点 tail = node; // 链表大小加1 size ++; return true; } /** * 在指定索引位置,插入节点 */ public boolean insertPos(int pos,E e){ // 判断索引位置有效性 if(pos < 1 || pos > size ){ return false; } // 创建节点 Node<E> node = new Node<E>(); node.setE(e); // 获取插入位置节点 Node<E> posNode = get(pos - 1); // 改变节点指针指向 node.next = posNode.next; posNode.next = node; // 链表大小加1 size ++; return true; } /** * 删除链表尾元素 */ public boolean remove(){ // 获取链表倒数第二个元素 Node<E> node = get(getSize() - 2); // 改变尾节点 tail = node; node.next = null; // 链表大小减1 size -- ; return true; } /** * 删除指定位置的元素(不能删除头节点) */ public boolean delPos(int pos){ // 判断索引位置有效性 if(pos < 1 || pos > size){ return false; } // 如果删除的是最后一个元素 if((pos + 1) == size){ remove(); }else{ // 获取删除元素节点 Node<E> node = get(pos); // 获取删除元素的前一个节点 Node<E> preNode = get(pos - 1); // 删除操作 preNode.next = node.next; // 链表大小减1 size --; } return true; } /** * 获取指定索引的链表节点 */ public Node<E> get(int index){ // 判断索引有效性 if(index < 0 || index > size - 1){ return null; } // 从头节点开始遍历 Node<E> node = head; for(int i=0; i< index; i++){ node = node.next; } return node; } /** * 获取指定索引位置的数据 */ public E getValue(int index){ // 获取节点 Node<E> node = get(index); if(node == null){ return null; } return node.e; } /** * 链表节点:Node<E> */ class Node<E>{ private E e; private Node<E> next; public E getE() { return e; } public void setE(E e) { this.e = e; } public Node<E> getNext() { return next; } public void setNext(Node<E> next) { this.next = next; } } }
package com.anan.struct.linetable; /** * 测试单链表 */ public class SingleLinkedListTest { public static void main(String[] args) { // 1.创建链表,添加元素 SingleLinkedList<Integer> list = new SingleLinkedList<Integer>(); for (int i = 0; i < 5; i++) { list.add(i); } System.out.println("1.创建链表,添加元素-----------------------------------------"); list(list); // 2.指定位置插入元素 System.out.println("2.指定位置【5】插入元素-----------------------------------------"); list.insertPos(5,666); list(list); // 3.删除链表结尾元素 System.out.println("3.删除链表结尾元素-----------------------------------------"); list.remove(); list(list); // 4.再次在链表结尾添加元素 System.out.println("4.再次在链表结尾添加元素-----------------------------------------"); list.add(888); list(list); // 5.删除指定位置元素 System.out.println("5.删除指定位置【1】元素-----------------------------------------"); list.delPos(1); list(list); } /** * 遍历输出链表 * @param list */ public static void list(SingleLinkedList<Integer> list){ System.out.println("当前链表大小,size:" + list.getSize()); for (int i = 1; i < list.getSize(); i++) { System.out.println(list.getValue(i)); } } }
测试结果:
D:\02teach\01soft\jdk8\bin\java com.anan.struct.linetable.SingleLinkedListTest 1.创建链表,添加元素----------------------------------------- 当前链表大小,size:6 0 1 2 3 4 2.指定位置【5】插入元素----------------------------------------- 当前链表大小,size:7 0 1 2 3 666 4 3.删除链表结尾元素----------------------------------------- 当前链表大小,size:6 0 1 2 3 666 4.再次在链表结尾添加元素----------------------------------------- 当前链表大小,size:7 0 1 2 3 666 888 5.删除指定位置【1】元素----------------------------------------- 当前链表大小,size:6 1 2 3 666 888 Process finished with exit code 0
#考考你答案:
1.你能用自己的话描述链表吗?
1.1.链表与数组一样,都是常用的基础数据结构
1.2.链表通过“指针”将一组零散的内存块串联起来使用
1.3.每一个零散的内存块称为:节点
1.4.链表的每个节点,除了存储数据以外,还需要存储一个指向下一个节点的指针
1.5.通常我们把指向下一个节点的指针,称为:后继指针
2.你知道链表和数组的区别吗?
2.1.数组需要连续的内存空间,链表不需要
2.2.插入、删除操作
2.2.1.数组需要移动数据,时间复杂度是:O(n)
2.2.2.链表不需要移动数据,时间复杂度是:O(1)
2.3.查找操作
2.3.1.数组支持随机访问操作,时间复杂度是:O(1)
2.3.2.链表需要从头节点遍历,不支持随机访问操作,时间复杂度是:O(n)
3.你知道都有哪些常用的链表吗?
3.1.单链表
3.2.双向链表
3.3.循环链表
我们唯一能够控制的是自己的脾气和努力