關於數據結構,這次總算下定決心進行一個總結了,之前各種零星散散的總結,並不成體系,這裏對一些基本的數據結構進行一個總結。大學學過數據結構的理論,但是一直沒用代碼完全實現過。
鏈表和結點的定義
節點的定義
/**
* autor:liman
* comment: 鏈表對應的節點
*/
public class ListNode {
public int value;
public ListNode next;
public ListNode(int value) {
this.value = value;
}
}
這個很簡單的定義。沒啥可說的,學過數據結構這個都很簡單
鏈表的定義
鏈表的定義就是在節點的基礎上,封裝一些基礎操作,一些插入刪除的操作百度就好,這裏直接貼出代碼
package com.learn.LinkList;
/**
* autor:liman
* comment:
*/
public class SelfLinkedList {
/**
* 頭結點的插入
* @param head
* @param newHead
*/
public static void headInsert(ListNode head,ListNode newHead){
ListNode old = head;
head = newHead;
head.next = old;
}
/**
* 未節點的插入
* @param tail
* @param newTail
*/
public static void tailInsert(ListNode tail,ListNode newTail){
ListNode old = tail;
tail = newTail;
newTail.next = null;
old.next = tail;
}
/**
* 遍歷鏈表
* @param head
*/
public static void traverse(ListNode head){
while(head!=null){
System.out.print(head.value+" ");
head = head.next;
}
System.out.println();
}
/**
* 查找節點,返回節點索引
* @param head
* @return
*/
public static int findNode(ListNode head,int value){
int index = -1;
int count = 0;
while(head!=null){
if(head.value == value){
index = count;
return index;
}
count++;
head = head.next;
}
return index;
}
/**
* 在pre節點的後面插入s節點
* @param pre
* @param s
*/
public static void insertAfterNode(ListNode pre,ListNode s){
ListNode pAfter = pre.next;
pre.next = s;
s.next = pAfter;
}
/**
* 刪除節點s,將s的next複製到s,然後刪除s的後繼節點
* @param head
* @param s
*/
public static void deleteNode(ListNode head,ListNode s){
if(s!=null && s.next!=null){//這裏不包含刪除尾節點的情況
ListNode sNext = s.next;
s.value = sNext.value;
//刪除s的下一個節點
s.next = sNext.next;
sNext=null;
}
//如果是刪除尾節點
if(s.next==null){
//遍歷找打前驅
while(head!=null){
if(head.next!=null && head.next == s){
head.next=null;
break;
}
head=head.next;
}
}
}
}
其中刪除節點的操作,找到前驅節點比較麻煩,這裏就偷了個懶,直接將後繼節點複製到當前節點,然後刪除當前節點。
一些實戰操作
鏈表翻轉
思路:利用三個指針依次走鏈,每走一步將隊伍後面兩個指針翻轉即可。
/**
* 翻轉鏈表,時間複雜度O(n),空間複雜度O(1)
*
* @param head
*/
public static ListNode reverseList(ListNode head) {
ListNode preNode = null;
ListNode afterNode = null;
while (head != null){
afterNode = head.next;
head.next=preNode;
preNode = head;
head = afterNode;
}
return preNode;
}
獲取中間節點
思路:取中間節點,如果鏈表長度是奇數,則直接取中間的。如果是偶數則取中間的前一個。
定義兩個指針,快指針每次走兩步,慢指針每次走一步,當快指針走到尾的時候,慢指針則爲中間節點。
/*
* @param head
* @return
*/
public static ListNode getMiddleNode(ListNode head){
if(head==null){
return head;
}
ListNode fastNode = head;
ListNode slowNode = head;
while(fastNode.next!=null && fastNode.next.next!=null){
slowNode = slowNode.next;
fastNode = fastNode.next.next;
}
return slowNode;
}
合併兩個有序鏈表
這個有幾種實現方式,一種是遞歸,一種是非遞歸。
遞歸實現
/**
* 遞歸的方式合併有序鏈表
*
* @param head01
* @param head02
* @return
*/
public static ListNode mergeTwoListRecursive(ListNode head01, ListNode head02) {
//遞歸出口
if (head01 == null && head02 == null) {
return null;
}
if (head01 == null) {
return head02;
}
if (head02 == null) {
return head01;
}
ListNode head = null;//合併後的頭節點
if (head01.value > head02.value) {
head = head02;
head.next = mergeTwoListRecursive(head01, head02.next);
} else {
head = head01;
head.next = mergeTwoListRecursive(head01.next, head02);
}
return head;
}
非遞歸實現
/**
* 非遞歸合併兩個鏈表。
*
* @param head01
* @param head02
* @return
*/
public static ListNode mergeTwoList(ListNode head01, ListNode head02) {
if (head01 == null || head02 == null) {
return head01 != null ? head01 : head02;
}
ListNode head = head01.value <= head02.value ? head01 : head02;
ListNode cur1 = head == head01 ? head01 : head02;
ListNode cur2 = head == head01 ? head02 : head01;
ListNode pre = null;
ListNode next = null;
while (cur1 != null && cur2 != null) {
if (cur1.value <= cur2.value) {//將cur1合併到目標鏈表
pre = cur1;
cur1 = cur1.next;
} else {//將cur2合併到目標鏈表
next = cur2.next;
pre.next = cur2;
cur2.next = cur1;
pre = cur2;
cur2 = next;
}
}
pre.next = cur1 == null ? cur2 : cur1;
return head;
}
非遞歸的另一種實現,我個人覺得這種思路要稍微簡單點
/**
* 合併鏈表,自己的思想,和當時的數據結構書籍上的處理一樣
*
* @param head01
* @param head02
* @return
*/
public static ListNode mergeTwoListSelf(ListNode head01, ListNode head02) {
if (head01 == null || head02 == null) {
return head01 != null ? head01 : head02;
}
ListNode pNode = new ListNode(-65535);//建立一個節點用於頭節點
ListNode head = head01.value <= head02.value ? head01 : head02;//構建head,後面的構建操作,交給了pNode
ListNode cur01 = head01;
ListNode cur02 = head02;
while (cur01 != null && cur02 != null) {
if (cur01.value <= cur02.value) {
pNode.next = cur01;
cur01 = cur01.next;
} else {
pNode.next = cur02;
cur02 = cur02.next;
}
pNode = pNode.next;
}
pNode.next = cur01 == null ? cur02 : cur01;
return head;
}
一些面試題
奇數升序,偶數降序
一個鏈表,奇數位按升序,偶數位按降序排列,對該鏈表進行排序。例如:1->8->3->6->5->4->7->2->9,需要對其排序
思路:1、按照奇數位和偶數位進行拆分,拆分成兩個鏈表。2、對偶數位出來的鏈表進行翻轉。3、合併排序
這裏直接貼出完整的實現
/**
* autor:liman
* createtime:2020/2/4
* 一個鏈表,奇數位升序,偶數位降序,對該鏈表進行排序
*/
public class InterviewTitleOne {
/**
* 分成三步:
* 1、按照奇數位和偶數位進行拆分
* 2、對偶數位進行翻轉
* 3、合併排序
*/
public static void main(String[] args) {
ListNode node01 = new ListNode(1);
ListNode node02 = new ListNode(8);
ListNode node03 = new ListNode(3);
ListNode node04 = new ListNode(6);
ListNode node05 = new ListNode(5);
ListNode node06 = new ListNode(4);
ListNode node07 = new ListNode(7);
ListNode node08 = new ListNode(2);
ListNode node09 = new ListNode(9);
node01.next = node02;
node02.next = node03;
node03.next = node04;
node04.next = node05;
node05.next = node06;
node06.next = node07;
node07.next = node08;
node08.next = node09;
ListNode[] listNodes = getList(node01);
ListNode head01 = listNodes[0];
ListNode head02 = listNodes[1];
//翻轉偶數位的鏈表
head02=reverseList(head02);
ListNode result = mergetList(head01,head02);
SelfLinkedList.traverse(result);
}
/**
* 拆分鏈表,按奇偶進行拆分
*
* @param head
* @return
*/
public static ListNode[] getList(ListNode head) {
ListNode head01 = null;
ListNode head02 = null;
ListNode cur01 = null;
ListNode cur02 = null;
int count = 1;
while (head != null) {
if (count % 2 == 1) {//奇數節點
if(cur01!=null){
cur01.next = head;
cur01 = cur01.next;
}else{
cur01 = head;
head01 = cur01;
}
}else{
if(cur02!=null){
cur02.next = head;
cur02 = cur02.next;
}else{
cur02 = head;
head02 = cur02;
}
}
head = head.next;
count++;
}
cur01.next = null;
cur02.next = null;
ListNode[] nodes = new ListNode[]{head01,head02};
return nodes;
}
/**
* 翻轉鏈表
* @param head
* @return
*/
public static ListNode reverseList(ListNode head){
ListNode pre = null;
ListNode next = null;
while(head!=null){
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
/**
* 合併兩個鏈表(遞歸實現)
* @param head01
* @param head02
* @return
*/
public static ListNode mergetList(ListNode head01,ListNode head02){
if(head01 == null && head02 ==null){
return null;
}
if(head01==null){
return head02;
}
if(head02 == null){
return head01;
}
ListNode head = null;
if(head01.value>head02.value){
head=head02;
head.next = mergetList(head01,head02.next);
}else{
head = head01;
head.next = mergetList(head01.next,head02);
}
return head;
}
}
其中的翻轉和合並操作之前已經介紹過,這裏不再贅述,這裏值得關注的是拆分操作。
實現鏈表的歸併排序
歸併排序應該算是鏈表排序最佳的選擇了,保證了最好和最好的時間複雜度都是nlogn,而且歸併排序在數組中空間複雜度爲O(n),在鏈表中也變成了O(1)。
思路:1、將待排序的數組(鏈表)一分爲二。2、遞歸的將左邊部分進行歸併排序。3、遞歸的將右邊部分進行歸併排序。4、將兩個部分進行合併排序,得到結果。
package com.learn.LinkList;
/**
* autor:liman
* createtime:2020/2/4
*/
public class InterviewTitleTwo {
public static void main(String[] args) {
ListNode node01 = new ListNode(1);
ListNode node02 = new ListNode(8);
ListNode node03 = new ListNode(3);
ListNode node04 = new ListNode(6);
ListNode node05 = new ListNode(5);
ListNode node06 = new ListNode(4);
ListNode node07 = new ListNode(7);
ListNode node08 = new ListNode(2);
ListNode node09 = new ListNode(9);
node01.next = node02;
node02.next = node03;
node03.next = node04;
node04.next = node05;
node05.next = node06;
node06.next = node07;
node07.next = node08;
node08.next = node09;
ListNode sortedList = sortList(node01);
SelfLinkedList.traverse(sortedList);
}
/**
* 鏈表的歸併排序算法
*
* @param head
* @return
*/
public static ListNode sortList(ListNode head) {
if (head == null || head.next==null) {//0個或者1個元素,不用排序,直接返回
return head;
}
ListNode middleNode = getMiddleNode(head);
ListNode rightFirst = middleNode.next;
middleNode.next=null;//拆成兩個鏈表;
ListNode node=merge(sortList(head),sortList(rightFirst));
return node;
}
/**
* 獲取中間節點
*
* @param head
* @return
*/
public static ListNode getMiddleNode(ListNode head) {
if (head == null) {
return head;
}
ListNode fastNode = head;
ListNode slowNode = head;
while (fastNode.next != null && fastNode.next.next != null) {
slowNode = slowNode.next;
fastNode = fastNode.next.next;
}
return slowNode;
}
/**
* 非遞歸有序合併兩個鏈表
*
* @param head01
* @param head02
* @return
*/
public static ListNode merge(ListNode head01, ListNode head02) {
if (head01 == null || head02 == null) {
return head01 != null ? head01 : head02;
}
ListNode head = head01.value <= head02.value ? head01 : head02;
ListNode cur1 = head == head01 ? head01 : head02;
ListNode cur2 = head == head01 ? head02 : head01;
ListNode pre = null;
ListNode next = null;
while (cur1 != null && cur2 != null) {
if (cur1.value <= cur2.value) {
pre = cur1;
cur1 = cur1.next;
} else {
next = cur2.next;
pre.next = cur2;
cur2.next = cur1;
pre = cur2;
cur2 = next;
}
}
pre.next = cur1 == null ? cur2 : cur1;
return head;
}
}
總結:
一些基礎操作的總結。