目录
单链表基础知识
单链表的特点
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;
}