介绍:
在同系列之前的文章中已经介绍过了线性结构和非线性结构,本篇文章我们就聊聊最基础的单链表
在看本文章之前,需要对数据结构有一个大体的了解,知道链表的基本结构和使用。
什么是单链表,又叫单方向链表,很明显的就是某一个节点之后之前或者之后一个索引,从前面遍历,就不能同时从后面遍历,
说白了就是链表元素内部只有一个单方向位置指针
实现:
package org.palm.hazelcast.laohanexcise;
/**
* @date 19-9-19
* @auther jackliang
* @description TODO
*/
public class SingleLinkedList {
public static void main(String[] args) {
HeroNode node1 = new HeroNode(1, "宋江", "及時雨");
HeroNode node2 = new HeroNode(2, "陸軍以", "玉麒麟");
HeroNode node3 = new HeroNode(3, "無用", "智多星");
HeroNode node4 = new HeroNode(4, "林從", "豹子頭");
SingleLinkedList list = new SingleLinkedList();
list.addByOrder(node1);
list.addByOrder(node4);
list.addByOrder(node2);
list.addByOrder(node3);
list.addByOrder(node3);
list.list();
System.out.println();
list.reverseLinkedList();
list.list();
System.out.println("修改后的链表情况");
HeroNode node = new HeroNode(2, "陸軍以!!", "玉麒麟!!");
list.update(node);
list.list();
System.out.println(list.getLength());
System.out.println(list.get(2));
System.out.printf("倒数第二个元素是=%s",list.getLastIndexNode(1));
list.remove(1);
list.remove(2);
list.remove(3);
list.remove(4);
list.list();
}
private HeroNode head = new HeroNode(0, "", "");
/*添加节点到单项列表尾部*/
public void add(HeroNode node) {
HeroNode temp = head;
//遍历链表
while (true) {
if (temp.next == null) {
//找到链表的最后了
break;
}
//如果没有找到最后,temp后移
temp = temp.next;
}
//当退出while循环,temp就指向了链表的最后
//将最后这个节点的next节点指向新的节点
temp.next = node;
}
/**
* 链表反转
*/
public void reverseLinkedList(){
HeroNode cur = head.next;
if (head.next == null && head.next.next == null){
return;
}
HeroNode next;
HeroNode reverseNode = new HeroNode(0, "", "");
//取出每一个结点的值,并放在新链表的最前端
while(cur != null){
next = cur.next;
cur.next = reverseNode.next; //当前节点的下一个节点指向链表的最前端
reverseNode.next = cur;//新链表的下一个节点指向当前节点
cur = next;//指针后移
/*这里有很多人不明白next这个暂存的变量是干嘛的,这里解释一下,next用来保存当前节点的下一个节点
* 一开始我是这么写的
cur.next = reverseNode.next; //当前节点的下一个节点指向链表的最前端
reverseNode.next = cur;//新链表的下一个节点指向当前节点
cur = cur.next;//指针后移
相信很多人也会这么写,但是如果不保存cur.next变量的话, 打印结果就只有第一个值,后面都没有了
循环的第二次 cur.next就是一个空值,因为第一次循环cur.next已经指向了反转节点
*/
}
head.next = reverseNode.next;
}
/*获取指定哪一个位置的元素*/
public HeroNode get(int index){
HeroNode temp = head;
if (temp.next == null){
System.out.println("链表为空");
}
if (index <= 0 && index > getLength()){
throw new IndexOutOfBoundsException();
}
for(int i = 0 ; i<index;i++){
temp = temp.next;//指针后移
}
return temp;
}
/*获得倒数第几个节点*/
public HeroNode getLastIndexNode(int index){
if (head.next == null){
System.out.println("没有找到");
}
HeroNode temp = head.next;
if (index <= 0 && index > getLength()){
throw new IndexOutOfBoundsException();
}
/*这里用总长度-要查看的索引位置得到需要移动几次的值*/
int pos = getLength() - index
for(int i=0 ; i < pos ; i++){
temp = temp.next;
}
return temp;
}
/*获取单链表的长度*/
public int getLength(){
HeroNode temp = head;
if (temp.next ==null){
System.out.println("节点为空");
}
int count = 0 ;
while (true){
if (temp.next == null){
break;
}
temp = temp.next;
count ++ ;
}
return count;
}
/*按顺序插入链表,这里是按照NO(编号)正序插入*/
public void addByOrder(HeroNode node) {
//因爲頭節點不能动,我们可以通过一个辅助指针来帮助找到添加的位置
//因此我们找的TEMP是位于添加位置的前一个节点。
HeroNode temp = head;
boolean flag = false;
//表示添加的标号是否存在
while (true) {
if (temp.next == null) {
//链表已到最后
break;
}
if (temp.next.no > node.no) {
//位置找到就在,temp后面插入
break;
} else if (temp.next.no == node.no) {
//编号已经存在
flag = true;
break;
}
temp = temp.next;
}
if (flag == true) {
//说明编号存在,不能加入
System.out.printf("准备加入的英雄编号%d已经存在,不能加入\n", node.no);
} else {
//新节点的next=temp.next
// temp.next=新节点
node.next = temp.next;
temp.next = node;
}
}
/*删除指定编号节点*/
public void remove (int no){
//1找到需要删除的这个结点的前一个节点temp
//2 temp.next = temp.next.next;
//3 被删除的节点被GC回收
if (head.next == null){
System.out.println("链表为空");
return;
}
HeroNode temp = head;
boolean flag = false;//是否找到待删除的节点
while(true){
if (temp.next == null){//已经到链表的最后
break;
}
if (temp.next.no == no){
//找到了待删除结点的前一个节点
flag = true;
break;
}
temp = temp.next;
//TEMP 后移
}
if (flag){//找到要删除节点的
temp.next = temp.next.next;
}else {
System.out.printf("要删除的%d节点不存在",no);
}
}
//修改节点信息根据no编号,即no编号不能改
/*更新节点*/
public void update(HeroNode newHeroNode){
if (head.next == null){
//link is null
System.out.println("链表为空");
return;
}
//找到需要修改的节点
//定义一个辅助变量
HeroNode temp = head.next;
boolean flag = false;
//是否找到该节点
while(true){
if (temp.next == null){
break;//到链表最后
}
if (temp.no == newHeroNode.no){
flag = true;
//找到退出
break;
}
temp = temp.next;
}
//根据flag判断是否要修改的节点
if (flag){
temp.nickName = newHeroNode.nickName;
temp.name=newHeroNode.name;
}else {
System.out.printf("没有找到编号%d的节点\n",newHeroNode.no);
}
}
/*展示节点*/
public void list() {
//判断链表为空
if (head.next == null) {
System.out.println("链表为空~");
return;
}
//因为头节点不能动,我们需要一个辅助变量来遍历
HeroNode temp = head.next;
while (true) {
if (temp == null) {
//链表最后
break;
}
System.out.println(temp);
//temp后移
temp = temp.next;
}
}
}
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
+ '}';
}
}
其中有些方法我是借鉴JDK双向链表linkedList写的,例如 get 方法,灵感来源如下:
上面的代码也很简单,如果实参大于长度的一半,从尾向前搜索,否则从头向后搜索和单链表的区别就是这已经是双链表了,但是处理方式的思想如出一辙。这样做很明显减半了时间复杂度,因为只用遍历一半
其中还有一个方法说明一下:reverseLinkedList
这个方法里边注释笔者应该写明白了,这里补充一下,如果不保存next暂存cur.next的地址的话打印结果只会有一个值如下:
这里只打印出了一号元素,为什么,因为第二次遍历的时候当前节点的下一个节点在第一次遍历的时候就指向了反转头节点,而后者是我们虚拟的工具节点,并没有实际的值,具体原因在注释里边描述了,应该好理解。