关于数据结构,这次总算下定决心进行一个总结了,之前各种零星散散的总结,并不成体系,这里对一些基本的数据结构进行一个总结。大学学过数据结构的理论,但是一直没用代码完全实现过。
链表和结点的定义
节点的定义
/**
* 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;
}
}
总结:
一些基础操作的总结。