链表操作总结

如何实现单链表的增删操作?

单链表的定义

//单链表结点
class Node{
//数据
int data;
//下一个结点
Node next=null;

public Node(int data){
this.data=data;
}

}

单链表的插入操作(增)

将值为x的新结点插入到单链表的第i个结点的位置上,即插入到数据元素Ai-1与Ai之间.
步骤如下:
1. 找到Ai-1的引用(存储位置) P
2. 生成一个数据域(data)为x的新节点 S
3. 设置S.next=P.next
4. 设置P.next=S

单链表的删除操作

删除第i个结点.
步骤如下:

  1. 找到A(i-1)的存储位置 P
    在这里插入图片描述
  2. 令P.next=Ai.next.

在这里插入图片描述
下面是一个实现了增加,删除,插入排序的经典单链表代码:

public class 简单的单链表实现 {
class Node{
	int data;
	Node next=null;
	public Node(int data) {
		// TODO Auto-generated constructor stub
	this.data=data;
	}
}
//头指针
Node head=null;
//添加操作
public void addNode(int d) {
	Node node=new Node(d);
	if (head==null) {
		head=node;
		return;
	}
	Node temp=head;
	while(temp.next!=null) {
		temp=temp.next;
	}
	temp.next=node;
}
//统计链表长度
public int length() {
	int length=0;
	Node temp=head;
	while(temp!=null) {
		length++;
		temp=temp.next;
	}
	return length;
}
//删除操作
public boolean deleteNode(int index) {
	if (index<1||index>length()) {
		return false;
	}
	if (index==1) {
		head=head.next;
		return true;
	}
	int i=1;
	Node preNode=head;
	Node curNode=preNode.next;
	while(curNode!=null) {
		preNode=curNode;
		curNode=curNode.next;
		i++;
		if (i==index) {
			preNode.next=curNode.next;
			return true;
		}
	}
	return true;
}
//进行插入排序
public Node orderList() {
	Node nextNode=null;
	int temp=0;
	Node curNode=head;
	while(curNode.next!=null) {
		nextNode=curNode.next;
		while(nextNode!=null) {
			if (curNode.data>nextNode.data) {
				temp=curNode.data;
				curNode.data=nextNode.data;
				nextNode.data=temp;
			}
			nextNode=nextNode.next;
		}
		curNode=curNode.next;
	}
	return head;
}
//输出链表
public void printList() {
	Node temp=head;
	while(temp!=null) {
		System.out.println(temp.data);
		temp=temp.next;
	}
}

public static void main(String[] args) {
	简单的单链表实现 list=new 简单的单链表实现();
	list.addNode(5);
	list.addNode(3);
	list.addNode(1);
	list.addNode(3);
	System.out.println("listLen="+list.length());
	System.out.println("before order:");
	list.printList();
	list.orderList();
	System.out.println("after order:");
	list.printList();
}
}

如何删除链表中重复的数据

我们可以把遍历到的值存储在一个HashTable中,在遍历过程中若当前访问的值在Hashtable中已存在就意味着这个数据是重复的,将其删除(比如1,4,3,5,1,删除重复后为1,4,3,5).
实现代码如下:

//Hashtable删除重复的数据
public void deleteDuplecate(Node head) {
	Hashtable< Integer, Integer> table= new Hashtable<>();
	Node temp=head;
	Node pre=null;
	while(temp!=null) {
		if (table.containsKey(temp.data)) {
			pre.next=temp.next;
		}else {
			table.put(temp.data, 1);
			pre=temp;
		}
		temp=temp.next;
	}
}

我们可以发现:使用hashtable存储记录消耗了额外的空间,那有没有一种算法能降低空间复杂度呢?

下面介绍一种双重循环遍历的方法:外循环正常遍历链表,假设外循环当前遍历的结点为cur,内循环就从cur开始遍历,若碰到与cur所指向的节点值相同,则删除这个重复结点,继续内循环直到链尾.
实现代码如下:

//双重循环删除重复数据
public void deleteDuplecate2(Node head) {
	Node pNode=head;
	while(pNode!=null) {
		Node qNode=pNode;
		while(qNode.next!=null) {
			if (pNode.data==qNode.next.data) {
				qNode.next=qNode.next.next;
			}else {
				qNode=qNode.next;
			}
		}
	}
}

如何找出单链表中的倒数第K个元素

最简单的解法是先知道链表的长度,即O(n),然后再从head执行到(n-k),遍历2次,时间复杂度为O(2n-k).

已知沿从头至尾的方向从链表中的某个元素开始,遍历K个元素后刚好到达链尾,那这个元素就是我们要找的.因此有:从头结点开始,依次对链表的每一个结点元素进行这种操作,时间复杂度为O(kn).

下面给出算法优化:
快慢指针法:由于单链表只能从头到尾访问各个结点,我们可以设置两个指针,让其中一个指针比另一个指针先移动K-1步,然后两个指针同时往前移动,直到**先走的指针 值为null.此时另一个指针就是我们要的倒数第K个元素.**时间复杂度O(n)

代码实现:

//快慢指针查找倒数第K个元素
public Node findElem(Node head,int k) {
	if (k<1||k>this.length()) {
		return null;
	}
	Node p1=head;
	Node p2=head;
	//前移k-1步
	for(int i=0;i<k-1;i++) {
		p1=p1.next;
	}
	while(p1!=null) {
		p1=p1.next;
		p2=p2.next;
	}
	return p2;
}

如何实现链表的反转

为了正确的反转链表,需要调整指针的指向.当调整指针的指向时链表就会断开,为了避免链表断开我们需要在调整Node.next的指向之前把Node.next的指向保存起来.

非递归方式链表反转的实现:

//反转链表
public void ReverseIteratively(Node head) {
//真正的头结点
	Node pReverseHead=head;
	//当前结点
	Node pNode=head;
	//当前结点的前结点
	Node pPrev=null;
	while(pNode!=null) {
	//当前结点的下结点
		Node pNext=pNode.next;
		if (pNext==null) {
			pReverseHead=pNode;
		}
		pNode.next=pPrev;
		pPrev=pNode;
		pNode=pNext;
	}
	this.head=pReverseHead;
}

如何从尾到头输出单链表

显而易见,只要实现了链表的反转,就能很容易的实现从尾到头的输出.那有没有不需要反转链表也能实现的算法呢?

我们可以使用栈的先进后出思想,通过额外空间: 栈 实现从尾到头的输出.

算法优化:
递归事实上也是一种栈结构.要实现反过来输出链表,只需要每访问一个结点,先递归它后面的结点,再输出该节点自身就可以了.

算法实现:

//递归实现倒叙
public void printListReversely(Node pListHead) {
	if (pListHead!=null) {
		printListReversely(pListHead.next);
		System.out.println(pListHead.data);
	}
}

如何寻找单链表的中间结点

常规思路是先求出链表的长度length,在遍历找出length/2的结点,即需要遍历两次.

算法优化:
我们在查找倒数第K个数已经见过了快慢指针的一种使用方法,下面介绍另一种用法:已知有指针A和指针B,当两个指针都从头节点开始时,每次遍历A走1步,B走2步,则A走的步数Ai永远是B走的步数Bi的一般,即Ai=1/2Bi,
推出:
A每次走a步,B每次走b步,A走的总步数为Ai,B走的总步数为Bi,i为走的次数,有结论:
Ai=a/b*Bi.
因此,要找出中间节点,只需A每次走一步,B每次走两步即可.

//查找中间节点
public Node SearchMid(Node head) {
	Node B=this.head;
	Node A=this.head;
	while(B!=null&& B.next!=null && B.next.next!=null) {
		B=B.next.next;
		A=A.next;
	}
	return A;
}

如何检测链表是否有环

要想直到链表是否有环就要知道什么是环?环就是永远读取不到Node.nextnull.
那么我们怎么证明链表有环还是无环呢?我们可以使用快慢指针,上面已经验证过
Ai=a/b*Bi,但这是在链表无环的情况下才成立的,如果有环就不会成立.
**因此,有两个指针A和B,A走1步,B走2步,如果在某一时刻A
B则有环,B.next==null则无环.**

实现代码如下:

//链表是否有环
public boolean isLoop(Node head) {
	Node B=head;
	Node A=head;
	if (B==null) {
		return false;
	}
	while(B!=null&&B.next!=null) {
		B=B.next.next;
		A=A.next;
		if (A==B) {
			return true;
		}
	}
	return !(B==null||B.next==null);
}

怎么找环入口

我们知道,当使用快慢指针证明链表有环时,A==B,B是快指针,这意味着B已经在环内走了n圈(n>=1).
假设,A指针走了S步,根据关系式我们知道B一定走了2S步(因为A走1步,B走2步).
有如下关系式:
2S=S+nr(r为环的步数长) ==> S=nr
设链表长度为L,环的入口与相遇点距离为x,起点到环的入口的距离为a,则有:
r=L-a
a+x=nr
a+x=(n-1)r+r=(n-1)r+L-a
a=(n-1)r+(L-a-x)

(L-a-x)为相遇点到环入口点的距离,从链表头head到环入口点a等于(n-1)循环内环+相遇点S到环入口点a,于是在链表头head与相遇点S分别设一个指针,每次各走一步,两指针必定相遇,且相遇位置为入口点.

实现代码:

//查找环入口点
public Node FindLoopPort(Node head) {
	Node A=head;
	Node B=head;
	while(B!=null&&B.next!=null) {
		A=A.next;
		B=B.next.next;
		if (A==B) {
			break;
		}
	}
	if (B==null || B.next==null) {
		return null;
	}
	A=head;
	while(B!=A)
	{
		B=B.next;
		A=A.next;
	}
	return A;
}

如何在不知道头指针的情况下删除指定节点

  1. 若待删除的结点为链表尾结点,则无法删除,因为删除后无法使其前驱结点的next指针置空.
  2. 若待删除的结点不是尾节点,则可以通过交换这个节点与其后继结点的值,然后删除后继结点(把该节点移出链表视为删除).

代码实现:

//在不传头节点的情况下删除结点
public boolean deleteNode(Node node) {
	if (node==null||node.next==null) {
		return false;
	}
	int temp=node.data;
	node.data=node.next.data;
	node.next.data=temp;
	node.next=node.next.next;
	return true;
}

如何判断两个链表相交

如果两个链表相交,那么它们一定有相同的尾结点.
我们可以分别遍历两个链表,记录它们的尾结点,如果尾结点相同则链表相交.

如果两个链表相交,找出相交的第一个结点

对两个链表进行遍历,让len1>len2,len1对应的链表为h1,len2对应的链表为h2.
先对h1遍历(len1-len2)个结点到结点p,此时结点p与h2到它们相交的结点的距离相同
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值