单链表

单链表基础知识

单链表的特点

1.链表的每一个元素都是节点类型,节点包括两个要素(数据data和下一个地址next)Node=data+next
2.节点与节点之间通过next连接
3.最后一个节点的引用为null

与数组区别

我们之前接触的数组是一个连续的地址空间,一般就是0,1,2······
单链表:非连续的地址空间通过next域把非连续空间的节点链起来变为一个链表
单链表的data域用于存储数据,next域指向下一个地址空间的地址
在这里插入图片描述

单链表基本功能的实现

a.创建一个单链表类

定义一个节点,这里代码以泛型为例

class MySingleList<T>{
	privat Node<T> head;//头节点,永远指向链表中的第一个节点
	//节点
	class Node<T>{
		private T data;//数据域
		private Node<T> next;//next引用域
		//构造函数
		public Node(T data){
			this.data = data;
		}
		//提供设置数据的方法
		public void setData(T data) {
            this.data = data;
        }
        //提供设置引用的方法
		public void setNext(Node<T> next){
			this.next = next;
		}
		//提供获取数据的方法
		public T getData() {
            return data;
        }
        //提供获取引用的方法
        public Node<T> getNext() {
            return next;
        }

b.单链表添加节点

1.尾插法

public void addTail(T value){
	//定义一个新的节点
	Node<T> newNade = new Node(value);
	//特殊情况,链表空的时候
	if(head == null){
		head = newNode;
	}else{
		//正常情况下,设计一个中间节点tmp遍历当前链表,把tmp赋值为head
		Node<T> tmp = head;
		//当遍历到尾结点时,节点引用为空
		while(tmp.next != null){
			//当还没有遍历到尾结点的时候,把tmp赋值为tmp的下一个节点的引用
			tmp = tmp.next;
		}
		//当tmp.next等于null的时候跳出循环
		//把需要插入的节点设置为tmp的下一个地址(引用)
		tmp.next = newNode;
		}
	}

2.头插法

public void addHead(T value){
	//在head节点之后添加一个新结点
	//定义一个新节点表示需要插入的节点
	Node<T> newNode = new Node(value);
	//判断,当单链表为空的时候
	if(head == null){
		head = newNode;
	}else{
		//一般情况,直接把头节点的引用指向要插入的节点,把原来头节点指向的引用存在新的节点的引用
		newNode.next = head.next;
		//先把head的引用储存在需要插入节点的next域,防止先断开就会丢失原来head节点指向的数据
		head.next = newNode;
	}
}

3.添加节点到指定位置

//先创建一个新的节点
Node<T> newNode = new Node(value);
public boolean addPos(T value,int pos){
	//参数合法性判断
	if(pos < 0 ||pos >= getLength(){
		return false;
	}
	//特殊情况,当pos等于0时
	if(pos == 0){
		newNode.next = head;
		head.next = newNode;
		return true;
	}
	//普通情况下,创建一个节点遍历单链表
	Node<T> tmp = head;
	//需要把节点插在pos位置,需要我们把pos-1的next引用指向新的节点,把新的节点的引用指向pos
	if(int i = 0;i <= pos-1;i++){
		tmp = tmp.next;
	}
	//当i=pos-1时,tmp=(pos-1).next
	//同样先把pos-1的next域引用先赋给newNode的next引用,再把pos-1的next域引用设为newNode
	newNode.next = tmp.next;
	tmp.next = newNode;
	return true;
}

c.单链表删除节点

public boolean remove(T value){
	//先考虑特殊情况
	if(head.equals(value)){
		//直接让head指向head.next
		head = head.next;
		return true;
		}
	//一般情况,首先定义tmp来遍历整个单链表
	Node<T> tmp = head;
	while(tmp != null){
		if(tmp.next.data.equals(value)){
			//tmp.next等于要删的值
			//tmp是需要删除之前的节点
			//tmp.next.next是需要删的节点的下一个节点
			tmp.next = tmp.next.next;
			return true;
			}
		tmp = tmp.next;
	}
	return false;
}

d.获取单链表的长度

public int getLength(){
	//定义长度初始值为0
	int length = 0;
	//定义一个tmp去遍历整个单链表
	Node<T> tmp = head;
	while(tmp != null){
		tmp = tmp.next;
		length++;
	}
	return length;
}

e.单链表查找节点

public T findNode(int index){
	//首先进行参数合法性判断
	if(index <= 0 ||index > getLength){
		return 0;
	}
	//定义tmp去遍历,通过index,找到需要的节点
	Node<T> tmp = head;
	for(int i=0;i<index;i++){
		tmp = tmp.next;
	}
	return tmp.data;
}

f.打印单链表(toString方法)

public String toString(){
	StringBuilder str = new StringBuilder();
	//定义tmp去遍历单链表
	Node<T> tmp = head;
    while(tmp != null){
    	strs.append(tmp.data + " ");
    	//.append连接字符串
        tmp = tmp.next;
    }
    return strs.toString();
}

单链表七大典型问题

1.不改变链表结构,逆序输出链表

先看看图,理解一下题意
在这里插入图片描述
下面我们来看一下递归的代码
回顾递归三要素
1.临界点
2.满足临界值的解决办法
3.提取相同的代码逻辑

public static <T> void reversePrintList(MySingleList<T>.Node<T> head){
	//临界点
	if(head == null){
	//满足临界值的解决办法
		return;
	}
	//相同的逻辑
    reversePrintList(head.getNext());
    System.out.println(head.getData());
}

2.改变链表结构,逆置链表

看图分析问题
在这里插入图片描述
代码

public static <T> MySingleList<T>.Node<T> reverseList(MySingleList<T>.Node<T> head){
	//定义三个节点,一个前驱,一个后继,一个当前节点
	//当前节点
	MySingleList<T>.Node<T> currentNode = head;
	//前驱
	MySingleList<T>.Node<T> last = null;
	//后继
	MySingleList<T>.Node<T> next = null;
	//逆置后头节点的引用应该指向空
	MySingleList<T>.Node<T> newHead = null;
	//让当前节点从头开始遍历
	while(currentNode != null){
		next = currentNode.getNext();
		//当遍历到最后一个节点时
		if(next == null){
			//逆置后头节点的引用指向最后一个节点
			newHead = currentNode;
		}
		//改变currentNode节点的引用指向它的前驱
		currentNode.setNext(last);
		last = currentNode;
        currentNode = next;
       }
    //返回新的头节点
    return newHead;
 }

3.不允许遍历节点,在指定节点前插入新结点

看图分析
在这里插入图片描述
代码

public static <T> boolean insertPosBefore(MySingleList<T> list,  MySingleList<T>.Node<T> pos, T value){
	//首先进行参数合法性判断
	if(list.getHead() == null || pos == null){
            return false;
    }
    //创建新节点
    MySingleList<T>.Node<T> newNode= list.createNode(pos.getData());
    //将新节点插入到pos之后
    pos.setNext(newNode);
    //改变pos的data域
    pos.setData(value);
    return true;
 }

4.查找单链表倒数第k个节点

方法一:
看图分析
在这里插入图片描述
代码

public static <T> MySingleList<T>.Node<T> findKNode(MySingleList<T>.Node<T> head, int k){
	//特殊情况
	if(head == null){
		return null;
	}
	//一般情况
	//第一步找出单链表节点个数count
	int count = 0;
	//定义一个tmp去遍历单链表
	MySingleList<T>.Node<T> tmp = head;
	while(tmp != null){
		count++;
		tmp = tmp.getNext();
	}
	//第二步找到倒数第k个节点
	//参数合法性判断
	if(k <= 0||k >count){
		return null;
	}
	//初始化tmp
	tmp = head;
	for(int i=0;i<count-k;i++)(
		tmp = tmp.getNext();
	}
	return tmp;
}

方法二:
先分析一下问题
在这里插入图片描述
代码

public static <T> MySingleList<T>.Node<T> findKNode1(MySingleList<T>.Node<T> head, int k){
	//特殊情况
    if(head == null){
         return null;
    }
    //一般情况
    //定义两个节点
    MySingleList<T>.Node<T> firstNode = head;
    MySingleList<T>.Node<T> currentNode = head;
    //参数合法性判断
    if(k <= 0){
    	return null;
    }
    //firstNode 先走k-1步
    for(int i=0;i<k-1;i++){
    	if(firstNode == null){
    		return null;
    	}
    	firstNode = firstNode.getNext();
    }
    //firstNode和currentNode同时走,等firstNode走到最后的时候,currentNode就是需要找的节点
    while(currentNode != null){
    	firstNode = firstNode.getNext();
    	currentNode = currentNode.getNext();
    }
    //返回当前节点
    return currentNode;
 }

5.合并两个有序单链表,保证新的链表有序

先看图,分析问题
在这里插入图片描述
代码

public static <T extends Comparable<T>> MySingleList<T>.Node<T> mergeOrderList(MySingleList<T>.Node<T> head1, MySingleList<T>.Node<T> head2) {
	//特殊情况
	//两个链表都为空
	if(head1 == null && head2 == null){
		return null;
    }else if(head1 == null){//一个链表为空时,直接返回另一个链表
    	return head2;
    }else if(head2 == null){
    	return head1;
    }
    //普通情况
    //先定义新链表的头节点
    MySingleList<T>.Node<T> newHead = null;
    //首先比较两个单链表的头节点来确定新链表的头节点
    if (head1.getData().compareTo(head2.getData()) >= 0) {
    	//当head1>=head2时,新的链表的头节点即为head2
    	newHead = head2;
    	head2 = head2.getNext();
    }else{
    	newHead = head1;
    	head1 = head1.getNext();
    }
    //接着去遍历两个链表,将其小的接在新的链表的头节点后边
    //定义tmp去遍历
    MySingleList<T>.Node<T> tmp = newHead;
    while (head1 != null && head2 != null) {
    	//比较大小
    	if (head1.getData().compareTo(head2.getData()) >= 0) {
    		tmp.setNext(head2);
    		//head2为更小的节点,继续往后走
    		head2 = head2.getNext();
            //tmp永远指向新链表最后一个节点
            tmp = tmp.getNext();
        }else{
        		tmp.setNext(head1);
                //head1为更小的位置点,继续往后
                head1 = head1.getNext();
                tmp = tmp.getNext();
            }
         }
        if (head1 == null) {
            tmp.setNext(head2);
        }
        if (head2 == null) {
            tmp.setNext(head1);
        }
        return newHead;
 }

6.两个节点相交,求出相交节点

先看图,分析问题
在这里插入图片描述
方法一:
先回顾一下栈的几个方法
1.push入栈
2.pop删除栈顶元素,并返回
3.peek返回栈顶元素,不删除

public static <T>  MySingleList<T>.Node<T> meetNode(MySingleList<T>.Node<T> head1, MySingleList<T>.Node<T> head2){n
	//特殊情况
	if(head1 == null || head2 == null){
		return null;
	}
    //正常情况
    //先定义两个栈
    Stack<MySingleList<T>.Node<T>> stack1 = new Stack<>();
    Stack<MySingleList<T>.Node<T>> stack2 = new Stack<>();
    //将两个链表进行入栈操作
    //list1
    //定义一个tmp去遍历该链表
    MySingleList<T>.Node<T> tmp = head1;
    while(tmp != null){
    	//入栈
    	stack1.push(tmp);
    	tmp = tmp.getNext();
    }
    //list2同list1一样入栈
    //先将tmp初始化
    tmp = head2;
     while(tmp != null){
     	stack2.push(tmp);
     	tmp = tmp.getNext();
     }
     //比较两个链表的data域
     //先定义交点为meetNode
     MySingleList<T>.Node<T> meetNode = null;
     while(stack1.peek() == stack2.peek()){
     	//两个栈内元素刚开始一定是一样的,所以meetNode等于1或者2的栈顶都可以
     	meetNode = stack1.peek();
     	//相同的树妖出栈继续比较下一个栈顶
     	stack1.pop();
     	stack2.pop();
     }
     return meetNode;
 }

方法二:

public static <T>  MySingleList<T>.Node<T> meetNode1(MySingleList<T> list1, MySingleList<T> list2){
 	//特殊情况
 	if(list1.getHead() == null || list2.getHead() == null){
    	return null;
    }
    //先求出两个链表的长度
    int length1 = list1.getLength();
    int length2 = list2.getLength();
    //定义两个链表的差值
    int d = Math.abs(length1 - length2);
    //定义两个链表的头节点
    MySingleList<T>.Node<T> longHead = list1.getHead();
    MySingleList<T>.Node<T> shortHead = list2.getHead();
    //找出长链表
    if(length1 < length2){
    	longHead = list2.getHead();
    	shortHead = list1.getHead();
    }
    //让长链表先走d个位置
    for(int i=0;i<d;i++){
    	longHead = longHead.getNext();
    }
    //同时走,当遇到两个节点相等时,即相交节点
    while(longHead != shortHead){
    	longHead = longHead.getNext();
    	shortHead = shortHead.getNext();
    }
    return longHead;
}

7.判断一个链表是否有环,求环的入口节点

代码

//首先判断是否有环
public static <T>  MySingleList<T>.Node<T> isRing(MySingleList<T>.Node<T> head){
	//特殊情况
	if(head == null){
		return null;
	}
    //定义快慢指针
     MySingleList<T>.Node<T> slow = head.getNext();
     //链表中只有一个节点
     if(slow == null){
         return null;
     }
     MySingleList<T>.Node<T> fast = slow.getNext();
     while(fast != null && fast.getNext() != null && slow != null){
     	//快指针和慢指针指向相同的节点(相遇)
	    if(slow == fast){
	        return slow;
        }
        //快指针走两步,慢指针走一步
        slow = slow.getNext();
        fast = fast.getNext().getNext();
     }
     return null;
}
 //求环的入口节点
public static <T> MySingleList<T>.Node<T> entranceNode(MySingleList<T>.Node<T> head){
	MySingleList<T>.Node<T> meetingNode = isRing(head); //meetingNode存在于环中
    //计算环中节点的个数
    int length = 1;
    MySingleList<T>.Node<T> tmp = meetingNode;
    while(tmp.getNext() != meetingNode){
        tmp = tmp.getNext();
        length++;
     }
     //front先走length  front, behind开始同时走  front != behind
     MySingleList<T>.Node<T> front = head;
     MySingleList<T>.Node<T> behind = head;
     for(int i=0; i<length ;i++){
     	front = front.getNext();
     }
     //front和behind同时走
     while(front != behind){
         front = front.getNext();
         behind = behind.getNext();
     }
     return front;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值