自己动手写一个单链表
文章有不当之处,欢迎指正,如果喜欢微信阅读,你也可以关注我的微信公众号:
好好学java
,获取优质学习资源。
一、概述
单向链表(单链表)是链表的一种,其特点是链表的链接方向是单向的,对链表的访问要通过顺序读取从头部开始。
链式存储结构的线性表将采用一组任意的存储单元存放线性表中的数据元素。由于不需要按顺序存储,链表在插入、删除数据元素时比顺序存储要快,但是在查找一个节点时则要比顺序存储要慢
使用链式存储可以克服顺序线性表需要预先知道数据大小的缺点,链表结构可以充分利用内存空间,实现灵活的内存动态管理。但是链式存储失去了数组随机存取的特点,同时增加了节点的指针域,空间开销较大。
二、图解
下图就是最简单最一般的单向链表:
这里写图片描述
新增节点:
将值为element的新节点插入到第index的位置上。
首先要先找到索引为index-1的节点,然后生成一个数据为element的新节点newNode,并令index-1处节点的next指向新节点,新节点的next指向原来index处的节点。
这里写图片描述
删除节点:
删除第index个节点,第index节点是由index-1出的节点引用的,因此删除index的节点要先获取index-1处的节点,然后让index-1出节点的next引用到原index+1处的节点,并释放index处节点即可。
这里写图片描述
三、单向链表的Java实现
下面的程序分别实现了线性表的初始化、获取线性表长度、获取指定索引处元素、根据值查找、插入、删除、清空等操作。
public class LinkList<T> { // 定义一个内部类Node,代表链表的节点 private class Node { private T data;// 保存数据 private Node next;// 指向下个节点的引用 // 无参构造器 public Node() { } // 初始化全部属性的构造器 public Node(T data, Node next) { this.data = data; this.next = next; } } private Node header;// 保存头结点 private Node tail;// 保存尾节点 private int size;// 保存已含有的节点数 // 创建空链表 public LinkList() { header = null; tail = null; } // 已指定数据元素创建链表,只有一个元素 public LinkList(T element) { header = new Node(element, null); // 只有一个节点,header,tail都指向该节点 tail = header; size++; } // 返回链表的长度 public int length() { return size; } // 获取指定索引处的元素 public T get(int index) { return this.getNodeByIndex(index).data; } //获取指定位置的节点 private Node getNodeByIndex(int index){ if(index < 0 || index > size-1){ throw new IndexOutOfBoundsException("索引超出线性表范围"); } Node current = header;//从header开始遍历 for(int i=0; i<size && current!=null; i++,current=current.next){ if(i == index){ return current; } } return null; } //按值查找所在位置 public int locate(T element){ Node current = header; for(int i=0; i<size && current!=null; i++, current=current.next){ if(current.data.equals(element)){ return i; } } return -1; } //指定位置插入元素 public void insert(T element, int index){ if(index < 0 || index > size){ throw new IndexOutOfBoundsException("索引超出线性表范围"); } //如果是空链表 if(header == null){ add(element); } else{ //当index为0时,即在链表头处插入 if(0 == index){ addAtHead(element); } else{ Node prev = getNodeByIndex(index - 1);//获取前一个节点 //让prev的next指向新节点,新节点的next指向原来prev的下一个节点 prev.next = new Node(element, prev.next); size++; } } } //在尾部插入元素 public void add(T element) { //如果链表是空的 if(header == null){ header = new Node(element, null); //只有一个节点,headwe,tail都该指向该节点 tail = header; } else{ Node newNode = new Node(element, null);//创建新节点 tail.next = newNode;//尾节点的next指向新节点 tail = newNode;//将新节点作为尾节点 } size++; } //头部插入 public void addAtHead(T element){ //创建新节点,让新节点的next指向header //并以新节点作为新的header Node newNode = new Node(element, null); newNode.next = header; header = newNode; //若插入前是空表 if(tail == null){ tail = header; } size++; } //删除指定索引处的元素 public T delete(int index){ if(index < 0 || index > size-1){ throw new IndexOutOfBoundsException("索引超出线性表范围"); } Node del = null; //若要删除的是头节点 if(index == 0){ del = header; header = header.next; } else{ Node prev = getNodeByIndex(index - 1);//获取待删除节点的前一个节点 del = prev.next;//获取待删除节点 prev.next = del.next; del.next = null;//将被删除节点的next引用置为空 } size--; return del.data; } //删除最后一个元素 public T remove(){ return delete(size - 1); } //判断线性表是否为空 public boolean isEmpty(){ return size == 0; } //清空线性表 public void clear(){ //将header,tail置为null header = null; tail = null; size = 0; } public String toString(){ if(isEmpty()){ return "[]"; } else{ StringBuilder sb = new StringBuilder("["); for(Node current = header; current != null; current = current.next){ sb.append(current.data.toString() + ", "); } int len = sb.length(); return sb.delete(len-2, len).append("]").toString(); } }
}
四、测试代码
import org.junit.Test;
import com.sihai.algorithm.LinkList;
public class LinkListTest { @Test public void test() { // 测试构造函数 LinkList<String> list = new LinkList("好"); System.out.println(list); // 测试添加元素 list.add("放大"); list.add("没"); System.out.println(list); // 在头部添加 list.addAtHead("啦啦啦"); System.out.println(list); // 在指定位置添加 list.insert("膜拜", 2); System.out.println(list); // 获取指定位置处的元素 System.out.println("第2个元素是(从0开始计数):" + list.get(2)); // 返回元素索引 System.out.println("膜拜在的位置是:" + list.locate("膜拜")); System.out.println("mobai所在的位置:" + list.locate("mobai")); // 获取长度 System.out.println("当前线性表的长度:" + list.length()); // 判断是否为空 System.out.println(list.isEmpty()); // 删除最后一个元素 list.remove(); System.out.println("调用remove()后:" + list); // 获取长度 System.out.println("当前线性表的长度:" + list.length()); // 删除指定位置处元素 list.delete(3); System.out.println("删除第4个元素后:" + list); // 获取长度 System.out.println("当前线性表的长度:" + list.length()); // 清空 list.clear(); System.out.println(list); // 判断是否为空 System.out.println(list.isEmpty()); }
}
五、链表相关的常用操作实现方法
1. 链表反转
/*** 链表反转* * @param head* @return*/public Node ReverseIteratively(Node head) {Node pReversedHead = head;Node pNode = head;Node pPrev = null;while (pNode != null) {Node pNext = pNode.next;if (pNext == null) {pReversedHead = pNode;}pNode.next = pPrev;pPrev = pNode;pNode = pNext;}this.head = pReversedHead;return this.head;}
2. 查找单链表的中间节点
采用快慢指针的方式查找单链表的中间节点,快指针一次走两步,慢指针一次走一步,当快指针走完时,慢指针刚好到达中间节点。
/*** 查找单链表的中间节点* * @param head* @return*/public Node SearchMid(Node head) {Node p = this.head, q = this.head;while (p != null && p.next != null && p.next.next != null) {p = p.next.next;q = q.next;}System.out.println("Mid:" + q.data);return q;}
3. 查找倒数第k个元素
采用两个指针P1,P2,P1先前移K步,然后P1、P2同时移动,当p1移动到尾部时,P2所指位置的元素即倒数第k个元素 。
/*** 查找倒数 第k个元素* * @param head* @param k* @return*/public Node findElem(Node head, int k) {if (k < 1 || k > this.length()) {return null;}Node p1 = head;Node p2 = head;for (int i = 0; i < k; i++)// 前移k步p1 = p1.next;while (p1 != null) {p1 = p1.next;p2 = p2.next;}return p2;}
4. 对链表进行排序
/*** 排序* * @return*/public Node orderList() {Node nextNode = null;int tmp = 0;Node curNode = head;while (curNode.next != null) {nextNode = curNode.next;while (nextNode != null) {if (curNode.data > nextNode.data) {tmp = curNode.data;curNode.data = nextNode.data;nextNode.data = tmp;}nextNode = nextNode.next;}curNode = curNode.next;}return head;}
5. 删除链表中的重复节点
/*** 删除重复节点*/public void deleteDuplecate(Node head) {Node p = head;while (p != null) {Node q = p;while (q.next != null) {if (p.data == q.next.data) {q.next = q.next.next;} elseq = q.next;}p = p.next;}}
6. 从尾到头输出单链表,采用递归方式实现
/*** 从尾到头输出单链表,采用递归方式实现* * @param pListHead*/public void printListReversely(Node pListHead) {if (pListHead != null) {printListReversely(pListHead.next);System.out.println("printListReversely:" + pListHead.data);}}
7. 判断链表是否有环,有环情况下找出环的入口节点
/*** 判断链表是否有环,单向链表有环时,尾节点相同* * @param head* @return*/public boolean IsLoop(Node head) {Node fast = head, slow = head;if (fast == null) {return false;}while (fast != null && fast.next != null) {fast = fast.next.next;slow = slow.next;if (fast == slow) {System.out.println("该链表有环");return true;}}return !(fast == null || fast.next == null);}/*** 找出链表环的入口* * @param head* @return*/public Node FindLoopPort(Node head) {Node fast = head, slow = head;while (fast != null && fast.next != null) {slow = slow.next;fast = fast.next.next;if (slow == fast)break;}if (fast == null || fast.next == null)return null;slow = head;while (slow != fast) {slow = slow.next;fast = fast.next;}return slow;}
参考资料:
- https://www.cnblogs.com/ganchuanpu/p/7468555.html
- https://blog.csdn.net/jianyuerensheng/article/details/51200274
自己动手写一个单链表相关推荐
- java 同步锁_死磕 java同步系列之自己动手写一个锁Lock
问题 (1)自己动手写一个锁需要哪些知识? (2)自己动手写一个锁到底有多简单? (3)自己能不能写出来一个完美的锁? 简介 本篇文章的目标一是自己动手写一个锁,这个锁的功能很简单,能进行正常的加锁. ...
- 用python 判断一个单链表是否有环
文章目录 用python 判断一个单链表是否有环. 第二次做DAY20201130 [141. 环形链表](https://leetcode-cn.com/problems/linked-list-c ...
- 如何逆置一个单链表(两种方法)?
在做关于单链表的一些算法题的时候,往往需要将单链表逆置后操作更加方便,但是一般说起来逆置,常用循环遍历单链表,使用头插法再次创建一个单链表实现逆置,但是这样不仅有点浪费存储空间,而且还容易搞混,那么如 ...
- java 手编线程池_死磕 java线程系列之自己动手写一个线程池
欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. (手机横屏看源码更方便) 问题 (1)自己动手写一个线程池需要考虑哪些因素? (2)自己动手写 ...
- 自己动手写一个印钞机 第四章
2019独角兽企业重金招聘Python工程师标准>>> 作者:阿布? 未经本人允许禁止转载 ipython notebook git版本 目录章节地址: 自己动手写一个印钞机 第一章 ...
- Spring Boot 动手写一个 Start
我们在使用SpringBoot 项目时,引入一个springboot start依赖,只需要很少的代码,或者不用任何代码就能直接使用默认配置,再也不用那些繁琐的配置了,感觉特别神奇.我们自己也动手写一 ...
- 单链表——判断一个单链表中是否有环
2019独角兽企业重金招聘Python工程师标准>>> package jxau.lyx.link;/*** * @author: liyixiang* @data:2014-10- ...
- 自己动手写一个nodejs的日志生成器
自己动手写一个nodejs的logger 最近正在边学边用node.js开发个人应用的server,由于有用到websocket相关,想对websocket的通信选择性的做下日志记录,所以萌发了自己动 ...
- 自己动手写一个印钞机 第二章
2019独角兽企业重金招聘Python工程师标准>>> 作者:阿布? 未经本人允许禁止转载 ipython notebook git版本 目录章节地址: 自己动手写一个印钞机 第一章 ...
最新文章
- CVPR和ICLR双榜公布,最离谱审稿人竟然没读论文!
- 如何找出电脑里的流氓软件_啥拦截软件都挡不住?教你一键揪出乱弹窗的流氓软件...
- 华为硬件工程师社招机考题库_中级会计机考你了解吗?机考操作常见八大问题速看...
- python做带数据库的登录界面_Python3 Tkinkter + SQLite实现登录和注册界面
- cordova与android通信_5:Cordova与原生交互--传值
- 在Java中从字符串中删除空格
- [日推荐]『蓝轨迹外语自学中心』免费的全能外语自学工具
- python 根据父子信息 还原成json树
- 通过jsp实现省市区县四级联动菜单
- 笔记本win10 1709 安装 v4w的教程
- java docx4j 使用教程_使用Docx4j操作PPT指南系列(二)
- 用c++语言写1加到100,C++ 语言实现1加到100(初学者)
- 创业板首批企业或节前招股 新公布6家上会公司
- snort:Packet结构体详解(留坑)
- dropbox与public
- 掌握 Dojo 工具包
- python base_Python base(一)
- 宏观低速物理 '牛顿篇'
- clk_mux及对应的约束
- throw 和 throws
热门文章
- 死锁的4个必要条件和处理策略
- Html 教程 (5) “表格”三要素
- java面试题4(基础)
- 用Go语言建立一个简单的区块链part1:基本原型
- (37)0环与3环通信常规方式,PspTerminateProcess 关闭进程工具
- WIN32获取进程当前目录
- [web安全]深入理解反射式dll注入技术
- 【渗透测试】一次运气很好的文件上传
- 异步I/O 设备内核对象,事件内核对象,可提醒I/O 接收I/O通知
- 【PAT乙级】 1010 一元多项式求导 (25 分)