目录
如:对于给定的单链表{1,2,3,4},将其重新排序为{1,4,2,3}.
一、实现单链表
1.1定义
采用链式存储方式的线性表称为链表,链表中每一个结点包含存放数据元素值的数据域和存放指向逻辑上相邻结点的指针域
若,一个结点只包含一个指针域,则称此链表为单链表。
1.2存储示意图
不带头结点,如图:
有时,为了操作方便,会在第一个节点之前虚加一个头结点,头结点的数据域不存放具体的值,指针域存放指向第一个结点(也叫:首结点)的指针,指向头结点的指针称为头指针,如图:
1.3抽象数据类型描述
public interface IList<T> {
void clear();//置空, 将一个线性表置空
boolean isEmpty();//判空, 空 返回 true,否则,返回false
int length();//长度 返回表中的数据元素
T get(int i);//取元素 返回线性表中的第i个元素,其中 0≤ i ≤ length()-1
void insert(int i,T x);//插入 在第i个位置插入元素x,其中 0≤ i ≤ length(),为0时,在表头插入,为length()在表尾插入
T remove(int i);//删除 移除第i个位置的元素,并返回该元素,其中 0≤ i ≤ length()-1
int indexOf(T x);//查找 查找并返回该元素第一次出现的位置,若不包含此元素,则返回-1
void display();//遍历 输出线性表中的所有元素
}
1.4实现
1.4.1 结点类
public class Node<T> {
private T data; //结点的数据域
private Node<T> next; //指针域
public Node(){} //构造 一个空的结点
public Node(T data){//构造 结点的数据域为指定数据,指针域为空
this.data = data;
}
public Node(T data,Node next){//构造 数据域和指针域均为 指定值的结点
this.data = data;
this.next = next;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public Node<T> getNext() {
return next;
}
public void setNext(Node<T> next) {
this.next = next;
}
}
1.4.2单链表类
public class MyLinkedList<T> implements IList<T> {
private Node<T> head; //单链表的头指针
public MyLinkedList(){
head = new Node<T>(); //初始化头结点
}
@Override
public void clear() { //将链表置为空表
head.setData(null);
head.setNext(null);
}
@Override
public boolean isEmpty() { //判断是否为空表
return head.getNext() == null;
}
@Override
public int length() { //长度
Node<T> p = head.getNext();
int length = 0;
while (p != null){
length ++;
p = p.getNext();
}
return length;
}
@Override
public T get(int i) throws Exception {//取值
if(i<0 || i>length()-1) throw new Exception("第"+ i+"个元素不存在!");
Node<T> node = head.getNext();
int j = 0;
while ( j < i && node.getNext() != null){
j++;
node = node.getNext();
}
return node.getData();
}
@Override
public void insert(int i, T x) throws Exception {//插入
Node<T> node = head;
int j =-1;
while (node != null && j<i-1){//得到第i-1位置的结点
node = node.getNext();
j ++;
}
if(j != i-1 || node == null) throw new Exception("插入位置有错!");
Node<T> newNode = new Node<T>(x);
newNode.setNext(node.getNext());
node.setNext(newNode);
}
@Override
public T remove(int i) throws Exception {//删除操作
if(i <-1 || i > length()-1) throw new Exception("当前删除位置非法!");
Node<T> node = head.getNext();
int j =0;
while(j < i-1 && node.getNext() != null){
node = node.getNext();
j++;
}
node.setNext(node.getNext().getNext());
return node.getData();
}
@Override
public int indexOf(T x) {//元素第一次出现的位置
Node<T> node = head.getNext();
int j =0;
//从首结点开始查找,直到找到x或到达尾部
while( node != null && !node.getData().equals(x) ){
node = node.getNext();
++j;
}
if(node == null) return -1;
return j;
}
@Override
public void display() {//遍历
Node<T> node = head.getNext();
while (node != null){
System.out.print(node.getData()+" ");
node = node.getNext();
}
System.out.println();
}
}
1.4.3测试类
public static void main(String[] args) {
IList<Integer> myList = new MyLinkedList<>();
int index =0;
Scanner sc = new Scanner(System.in);
while (true){
System.out.println("************我的单链表***********");
System.out.println("**1.是否为空表** ** 2.表的长度**");
System.out.println("**3.取元素** ** 4.插入元素**");
System.out.println("**5.删除元素** ** 6.查找元素**");
System.out.println("**7.遍历** ** 8.置为空表**");
System.out.print("请输入:");
int input = sc.nextInt();
switch (input){
case 1://判空
if(myList.isEmpty()) System.out.println("空表!");
else System.out.println("非空表!");
break;
case 2://长度
System.out.println("表的当前长度为:"+myList.length());
break;
case 3://取元素
System.out.print("请输入位置:");
index = sc.nextInt();
try {
System.out.println("第"+index+"个元素为:"+myList.get(index));
} catch (Exception e) {
e.printStackTrace();
}
break;
case 4://插入元素
System.out.print("请输入插入位置:");
index = sc.nextInt();
System.out.print("请输入插入元素:");
input = sc.nextInt();
try {
myList.insert(index,input);
} catch (Exception e) {
e.printStackTrace();
}
break;
case 5://删除元素
System.out.print("请输入待删除元素的位置:");
index = sc.nextInt();
try {
System.out.println("删除的元素为"+myList.remove(index));
} catch (Exception e) {
e.printStackTrace();
}
break;
case 6://查找元素
System.out.print("请输入查找的值:");
input = sc.nextInt();
int i = myList.indexOf(input);
if(i == -1) System.out.println("没有找到!");
else System.out.println("找到了,首次出现的位置为:"+i);
break;
case 7://遍历
myList.display();
break;
case 8://置为空表
myList.clear();
break;
}
}
}
二、单链表的插入排序
思想:插入排序->左边为 有序 ;右边为 待排序
定义两个指针:pre cur
pre 为:有序的尾指针;cur 为:待排序的首指针;
过程:
判断条件:cur!= null
将cur与pre比较;
若已有序,将pre= cur,cur=cur.next
若不符合序,将cur从链表中删除,pre.next==cur.next
然后将cur结点从首结点开始比较,找到插入位置,将cur插入有序的链表
public Node insertSort() {//插入排序
Node pre = head.getNext(); //head为头结点
if( pre == null || pre.getNext() == null) return head;
Node cur = head.getNext().getNext();
Node q1 = null;
Node q2 = null;
while (cur != null){
if(cur.getData() < pre.getData()){
pre.setNext(cur.getNext());//删除该结点
q1 = head;
q2 = head.getNext();
while (cur.getData() > q2.getData()){
q1 = q2;
q2 = q2.getNext();
}
cur.setNext(q2);
q1.setNext(cur);
cur = pre.getNext();
}else{
pre = cur;
cur = cur.getNext();
}
}
return head;
}
leetCode:https://www.nowcoder.com/questionTerminal/152bc6c5b14149e49bf5d8c46f53152b
三、指定排序
将给定的单链表L: L 0→L 1→…→L n-1→L n,
重新排序为: L 0→L n →L 1→L n-1→L 2→L n-2→…
如:对于给定的单链表{1,2,3,4},将其重新排序为{1,4,2,3}.
思想:
找到中间结点(通过快慢指针法,快每次一个,慢每次两个),将该链表分为两个链表(即前后),得到的中间结点作为后链表的头结点;1 -> 2 3->4
将后半链表逆序(通过前插法进行逆序)4 -> 3
将后链表插入逐个到前链表的后边(后插法) 1 -> 4 -> 2 -> 3
public void reorderList(ListNode head) {
//为空 || 链表中有一个值 || 链表中有两个值
if(head == null || head.next == null || head.next.next == null) return ;
//得到中间结点
ListNode p1 = head,p2 = head;
do{
p1 = p1.next;
p2 = p2.next.next;
}while(p2 != null && p2.next != null && p2.next.next != null);
//得到后半部分链表
ListNode midList = new ListNode(-1); // 头结点,指向后半部分
midList.next = p1.next;
//将后半部分的链表逆序 == 通过头插法
ListNode pre = midList.next;//首结点
ListNode cur = midList.next.next;
while(cur!= null){
pre.next = cur.next; //将此结点删除
cur.next = midList.next;
midList.next = cur;
cur = pre.next;
}
//将前后两部分的链表合并 == 将后半部分的插入到前半部分的后边【后插】
//得到前半部分链表 == head
p1.next = null;
//前半部分的首结点为 head ; 后半部分的首结点为 midList.next
ListNode pre2 = midList.next;
while(pre2 != null){
midList.next = pre2.next;//删除该结点
pre2.next = head.next;
head.next = pre2;
head = head.next.next;
pre2 = midList.next;
}
}
leetCode:https://www.nowcoder.com/questionTerminal/3d281dc0b3704347846a110bf561ef6b
四、反转链表
思想:通过前插法(pre cur两个指针实现)
leetCode:https://www.nowcoder.com/questionTerminal/d0267f7f55b3412ba93bd35cfa8e8035
输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> list = new ArrayList<Integer>();
if(listNode == null) return list;
ListNode head = new ListNode(0);
head.next = listNode;
ListNode pre = listNode;
ListNode cur = listNode.next;
while(cur != null){
pre.next = cur.next;
cur.next = head.next;
head.next = cur;
cur = pre.next;
}
while(head.next != null){
list.add(head.next.val);
head = head.next;
}
return list;
}
五、输出单链表中的倒数第K个结点
思想:
定义 p1 ,p2 分别指向链表的首结点 ps:如链表为:1 2 3 4 5,得到倒数第2个结点 ,即 4
当p1链表遍历到第2个时,进入下一个循环,使用p2链表,(条件:p1.next != null)如图
即p2.next 为所求;
leetCode:https://www.nowcoder.com/questionTerminal/529d3ae5a407492994ad2a246518148a
public ListNode FindKthToTail(ListNode head,int k) {
int length =0;
ListNode headLen = head;
while(headLen != null){
length++;
headLen = headLen.next;
}
if( k <1 || k>length) return null;
ListNode p1 = head,p2 = head;
int count=0;
while(p1!= null){
count++;
if(count == k){
p1 = p1.next;
while(p1 != null){
p1 = p1.next;
p2 = p2.next;
}
break;
}else{
p1 =p1.next;
}
}
return p2;
六、反转单链表
leetCode:https://www.nowcoder.com/questionTerminal/75e878df47f24fdc9dc3e400ec6058ca
public ListNode ReverseList(ListNode head) {
if(head == null) return null;
ListNode liftHead = new ListNode(-1);
liftHead.next = head;
ListNode pre = head;
ListNode cur = pre.next;
while(cur != null){
pre.next = cur.next;
cur.next = liftHead.next;
liftHead.next = cur;
cur = pre.next;
}
return liftHead.next;
}
七、单链表中是否有环?环的起始结点?环的长度?
7.1判断环
思想:
快慢指针法:p1 = p1.next;p2 = p2.next.next;当有环时,肯定存在 p1 == p2
if(head == null || head.getNext() == null) return false;
Node per = head.getNext(),cur = head.getNext().getNext();
while(cur.getNext()!= null){
if( cur == per ) return true;
per = per.getNext();
cur = cur.getNext().getNext();
}
return false;
7.2判断环的起始结点
思想:
当存在环时,我们得到,p1 == p2 时的结点p1,
将p2从头结点开始遍历,定义 p3 = p1.next,与p4 = head.next同时遍历
当在一次 p3 == p4时,该结点为环的起始结点
if(head == null || head.getNext() == null) return null;
Node p1 = head.getNext(),p2 = head.getNext().getNext();
Boolean flag = false;//无环
while (p2.getNext() != null){
if(p1 == p2){
flag = true;
break;
}
p1 = p1.getNext();
p2 = p2.getNext().getNext();
}
if(!flag) return null;
Node p3 = p1.getNext();
Node p4 = head.getNext();
while (p3 != p4){
p3 = p3.getNext();
p4 = p4.getNext();
}
return p3;
7.3判断环的长度
思想:当两个链表中,再次相遇时,那么第一次相遇与再次相遇之间的结点个数即为长度
leetCode:https://www.nowcoder.com/questionTerminal/253d2c59ec3e4bc68da16833f79a38e4
public ListNode EntryNodeOfLoop(ListNode pHead){
if(pHead == null || pHead.next == null) return null;
//判断是否有环 == 也就得到了第一次相遇的结点
ListNode p1 = pHead,p2 = pHead.next;
Boolean flag = false;
while(p2.next != null){
if(p1 == p2) {
flag = true;
break;
}
p1 = p1.next;
p2 = p2.next.next;
}
if(!flag) return null;
ListNode p3 = p1.next,p4 = pHead;
while(p3 != p4){
if(p3 == p4) break;
p3 = p3.next;
p4 = p4.next;
}
return p3;
}
八、删除单链表中重复的元素
如:0 -> 1->1->2 0->2
public ListNode deleteDuplication(ListNode pHead){
if(pHead == null || pHead.next == null) return pHead;
ListNode root = new ListNode(-999);
root.next = pHead;
ListNode pre = root,cur = root.next;
while(cur != null && cur.next != null){
if(cur.val == cur.next.val){
while(cur.next != null && cur.val == cur.next.val){
cur = cur.next;
}
pre.next = cur.next;
}else{
pre = cur;
}
cur = cur.next;
}
return root.next;
}