引言:生活中,我们离不开排序。比如体育课时按身高进行排队排序,又或者学校录取学生时按照成绩从大到小排序录取,等等; 而且面试中排序算法也是比较常问的算法(以下排序默认从小到大)…
排序算法分类
根据时间复杂度分类:
- 时间复杂度为O(n^2)
- 冒泡排序
- 选择排序
- 直接插入排序
- 希尔排序(比较特殊时间复杂度在O(nlogn)和O(n^2)之间)
- 时间复杂度为O(nlogn)
- 快速排序
- 堆排序
- 归并排序
- 时间复杂度为线性阶O(n)
- 桶排序
- 基数排序
根据稳定性分类:
稳定性:对于序列a中值相等的元素a[i]=a[j],i<j;排序以后的序列中a[i]、a[j]的前后位置不变则为稳定,反之则为不稳定。
- 稳定排序:直接插入排序、折半选择排序、冒泡排序、归并排序、基数排序
- 不稳定排序:堆排序、快速排序、希尔排序、直接选择排序
冒泡排序
对于序列中两两相邻的元素进行比较,逐步将两个相邻元素之间的最大值往后移动,直到将最大值移动到最右边,将次大值移动到次最右边…,像冒泡一样,将泡泡大的浮动到最上方。
//todo 1.冒泡排序
public static void maopao(int[] arr){
int len=arr.length;
for (int i=1;i<len;i++){
boolean is_sort=true; //假设有序
for (int j=0;j<len-i;j++){
if (arr[j]>arr[j+1]){ //交换相邻元素
arr[j]=arr[j]^arr[j+1];
arr[j+1]=arr[j]^arr[j+1];
arr[j]=arr[j]^arr[j+1];
is_sort=false;
}
}
if (is_sort){ //如果这一轮没有交换说明已经有序了,直接退出
break;
}
}
}
选择排序
思路:每一轮选出最小元素的索引,然后直接将最小元素索引与左边元素交换,这种排序的最大优势就是省去了类似于冒泡排序的多次交换。
//todo 2.选择排序
public static void change(int[] arr){
int len=arr.length;
for (int i=0;i<len;i++){
int minIndex=i; //选定最小元素索引
for (int j=i+1;j<len;j++){
if (arr[j]<arr[minIndex])
minIndex=j;
}
if (minIndex != i){
arr[minIndex]=arr[minIndex]^arr[i];
arr[i]=arr[minIndex]^arr[i];
arr[minIndex]=arr[minIndex]^arr[i];
}
}
}
直接插入排序
思路:维护一个有序区,把数组中的元素一个个插入有序区的适当位置,直到数组所有元素有序为止。
//todo 3.直接插入排序
public static void insert_sort(int[] arr){
int len=arr.length;
for (int i=1;i<len;i++){
int j=i-1;
int tmp=arr[i]; //将当前元素拷贝,用以与之前元素比较插入
while (j>=0 && tmp<arr[j]){
arr[j+1]=arr[j]; //将较大元素往后移动,以备当前元素插入
j--; //逐步向之前元素进行比较
}
arr[j+1]=tmp;
}
}
快速排序
思路:在每一轮挑选一个基准元素pivot,并让其他比它大的元素移动到pivot右边,小的元素移动到左边,从而把数组拆解成两部分;再将两部分递归排序----分治法。
//todo 4.快速排序
public static void quick_sort(int[] arr,int left,int right){
if (left>right)
return;
int i=left;
int j=right;
int pivot=arr[left];
while (i<j){
while (i<j && arr[j]>pivot)
j--;
while (i<j && arr[i]<=pivot)
i++;
if (i<j){
arr[i]=arr[i]^arr[j];
arr[j]=arr[i]^arr[j];
arr[i]=arr[i]^arr[j];
}
}
//将基准元素与重合元素交换
arr[left]=arr[i];
arr[i]=pivot;
quick_sort(arr,left,i-1); //将左边部分递归排序
quick_sort(arr,i+1,right); //将右边部分递归排序
}
归并排序
思路:归并排序是把序列递归地分成短序列(分组),递归出口是短序列只有1个元素(认为直接有序)或者2个序列(1次比较和交换),然后把各个有序的段序列合并成一个有序的长序列(归并),不断合并直到原序列全部排好序。
//todo 5.归并排序
//5.1 数组分组
public static void split_arr(int[] arr,int left,int right){
if (left < right){
int middle=left+(right-left)/2; //防止(right+left)/2 数据溢出
//左边分组
split_arr(arr,left,middle);
//右边分组
split_arr(arr,middle+1,right);
//分组合并排序
merge_arr_sort(arr,left,middle,right);
}
}
//5.2 数组合并排序
public static void merge_arr_sort(int[] arr,int left,int middle,int right){
int[] tmp=new int[right-left+1]; //存储合并的数组
int i=left;
int j=middle+1;
int t=0;
while (i<=middle && j<=right){
if (arr[i]<arr[j]){
tmp[t++]=arr[i++];
}else tmp[t++]=arr[j++];
}
while (i<=middle) //当左边部分还有剩余元素(剩余元素因为之前的排序已经有序)时直接插入临时数组
tmp[t++]=arr[i++];
while (j<=right) //当右边部分还有剩余元素时直接插入临时数组
tmp[t++]=arr[j++];
//将合并的数组拷贝到原数组中
System.arraycopy(tmp,0,arr,left,tmp.length);
}
希尔排序
思路:当数组中大部分元素有序时,插入排序的工作量相对较小,数组中的元素并不需要进行频繁的比较与交换。
//todo 6.希尔排序
public static void shell_sort(int[] arr){
int len=arr.length;
for (int d=len/2;d>0;d/=2){ //d:增量
for (int i=d;i<len;i++){
int j=i-d;
int tmp=arr[i];
while (j>=0 && tmp<arr[j]){
arr[j+d]=arr[j];
j -= d;
}
arr[j+d]=tmp;
}
}
}
桶排序
思路:将数组分到有限数量的桶里。每个桶再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序),最后依次把各个桶中的记录列出来记得到有序序列。
//todo 7.桶排序
public static int[] bucket_sort(int[] arr){
int len=arr.length;
//1.求极值
int max=arr[0];
int min=arr[0];
for (int i=1;i<len;i++){
if (arr[i]<min)
min=arr[i];
if (arr[i]>max)
max=arr[i];
}
int d=max-min;
//2.对桶进行初始化
ArrayList<LinkedList<Integer>> buckets = new ArrayList<>(len);
for (int i=0;i<len;i++){
buckets.add(new LinkedList<>());
}
//3.对数组中元素存放到对应桶中(将桶中元素映射到0~len-1的索引中)
for (int x:arr){
int index=(int)((x-min)/d*(len-1));
buckets.get(index).add(x);
}
//4将各个桶中所有元素排序
for (int i=0;i<len;i++){
Collections.sort(buckets.get(i));
}
//5输出各元素
int[] result = new int[len];
int t=0;
for (int i=0;i<len;i++){
for (int j=0;j<buckets.get(i).size();j++)
result[t++]=buckets.get(i).get(j);
}
return result;
}
堆排序
堆是一种叫做完全二叉树的数据结构,可以分为大根堆,小根堆,而堆排序就是基于这种结构而产生的一种程序算法。
//todo 8.堆排序
/*
* @param arr 要维护的数组
* @param parentIndex 要维护的节点
* @param len 要维护的数组长度
* */
public static void heapify(int[] arr,int parentIndex,int len){
//计算左右子节点
int leftChildren = 2*parentIndex+1;
int rightChildren = leftChildren+1;
//求最大节点的索引
int largest = parentIndex;
if (leftChildren<len && arr[leftChildren]>arr[largest])
largest = leftChildren;
if (rightChildren<len && arr[rightChildren]>arr[largest])
largest = rightChildren;
if (largest != parentIndex){
arr[largest]=arr[largest]^arr[parentIndex];
arr[parentIndex]=arr[largest]^arr[parentIndex];
arr[largest]=arr[largest]^arr[parentIndex];
//维护parentIndex节点
heapify(arr,largest,len);
}
}
//创建最大堆
public static void heap_sort(int[] arr){
int len=arr.length;
for (int i=len/2-1;i>=0;i--){
//最后一个节点索引为len-1,其对应的父节点带入(n-1)/2即得:len/2-1
//从最后一个父节点开始维护最大堆性质
heapify(arr,i,len);
}
//选择最大元素进行交换
for (int i=len-1;i>0;i--){
arr[i]=arr[i]^arr[0];
arr[0]=arr[i]^arr[0];
arr[i]=arr[i]^arr[0];
//将互换以后的堆顶维护性质
heapify(arr,0,i);
}
}
面试1:
有个文件,文件的每一行是书信息数据,分4个部分用逗号(,)进行分割,格式如下
id,category,words,updatetime
id 表示书id,long类型,id不重复;
category 表示书的分类,int类型,请注意全部数据的分类只有几个
words 表示书的字数,int类型
updatetime 表示书的更新时间 ,格式为2020-02-01 23:00:00
4
66,20002,25919,2020-02-16 17:35:00
63,20004,9914,2020-02-16 17:35:00
60,20001,1982,2020-02-16 17:35:00
68,20004,1693,2020-02-16 17:35:00
请编写程序对文件数据进行排序后输出id,排序优先级为: category>updatetime > words > id , 增序排序
public class Demo3 {
public static void main(String[] args) throws Exception {
//时间转换格式
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
ArrayList<Book> books = new ArrayList<>();
//获取文件数据流
File file = new File("G:/t1.txt");
try (BufferedReader bufferedReader = new BufferedReader(new FileReader(file))) {
List<String> list =bufferedReader.lines() //bufferedreader读取文件数据
.filter(e -> e.length() > 4)
.collect(Collectors.toList());
for (String e:list){
String[] fields = e.split(",");
books.add(new Book(Long.parseLong(fields[0]),Integer.parseInt(fields[1]),
Integer.parseInt(fields[2]),dateFormat.parse(fields[3]).getTime()));
}
books.sort(new Comparator<Book>() {
@Override
public int compare(Book o1, Book o2) {
if (o1.getCategory()!=o2.getCategory()){
return o1.getCategory()-o2.getCategory();
}else if (o1.getUpdatetime()!= o2.getUpdatetime()){
return (int) (o1.getUpdatetime()-o2.getUpdatetime());
}else if (o1.getWords()!=o2.getWords()){
return o1.getWords()-o2.getWords();
}else return (int) (o1.getId()-o2.getId());
}
});
books.forEach(e->{
System.out.println(e.getId());
});
// 结果:60 66 68 63
}
}
static class Book{ //将文件中的数据封装成书籍类
private long id;
private int category;
private int words;
private long updatetime;
//todo 省略set、get方法,无参有参方法
}
}
面试2:合并两个有序数组,成为一个新的有序数组
public class test2 {
public static void main(String[] args) {
// 合并两个有序数组,成为一个新的有序数组
int[] arr1 = {10,18,22,40,56};
int[] arr2 = {7,12,32,50,54};
System.out.println("合并以后的数组为:"+Arrays.toString(merge_arr(arr1,arr2)));
}
//参考的归并排序中的归并操作
private static int[] merge_arr(int[] arr1, int[] arr2) {
//创建临时数组用以存储合并两个数组的元素
int[] tmp = new int[arr1.length + arr2.length];
int t=0;
int i=0,j=0;
while (i<arr1.length && j<arr2.length){
if (arr1[i]<arr2[j]){
tmp[t++]=arr1[i++];
}else tmp[t++]=arr2[j++];
}
while (i<arr1.length)
tmp[t++]=arr1[i++];
while (j<arr2.length)
tmp[t++]=arr2[j++];
return tmp;
}
}
面试3:在一个N个整数数组里面,有多个奇数和偶数,设计一个排序算法,令所有的奇数都在左边。
public static void get_result2(int[] arr){
//设置两个指针p1,p2指向头和尾,直到p1为偶数,p2为奇数时交换两指针的值(参考了快速排序)
int p1=0,p2=arr.length-1;
while (p1<p2){
while ((arr[p1]&1)!=0)
p1++;
while ((arr[p2]&1)==0)
p2--;
if (p1<p2){
arr[p1]=arr[p1]^arr[p2];
arr[p2]=arr[p1]^arr[p2];
arr[p1]=arr[p1]^arr[p2];
}
}
}
面试:链表的创建、删除、排序、反转等操作
public class test7 {
//todo size标记链表长度(节点个数)
private static int size=0;
public static void main(String[] args) {
//创建链表
Node head = create_link(5);
show_link(head);
System.out.println("获取第2个节点"+get_index(2+1,head));
System.out.println("向链表第三个位置插入333:");
insert(3, 333, head);
show_link(head);
System.out.println("向链表头部插入0:");
head = insert(1, 0, head);
show_link(head);
System.out.println("向链表尾部插入999:");
head = insert(size, 999, head);
show_link(head);
System.out.println("删除头部节点:");
head = delete_index(1, head); //删除头部节点
show_link(head);
System.out.println("删除尾部节点:");
head = delete_index(size, head);
show_link(head);
System.out.println("删除第三个节点:");
head = delete_index(3, head);
show_link(head);
System.out.println("链表反转后:");
head = recursion_reverse(head);
show_link(head);
System.out.println("排序以后的链表:");
show_link(maopao_sort(head));
}
//todo 向链表中第index个位置插入element元素
public static Node insert(int index,int element,Node head){
if (index<=0 || index>size){
throw new IndexOutOfBoundsException("插入位置有误!");
}
if (index==1){ //插入头部
Node current = new Node(element);
current.next=head;
head=current;
}else if (index==size){ //插入尾部
Node pre_last = get_index(index, head);
pre_last.next.next=new Node(element);
}else { //插入中间部分
//先获取第index-1的节点
Node pre_index = get_index(index, head);
Node current = new Node(element);
current.next=pre_index.next;
pre_index.next=current;
}
size++;
return head;
}
//TODO 头插法创建链表
public static Node create_link(int n){
size=n;
Node head=null;
Scanner scanner = new Scanner(System.in);
System.out.println("输入链表data:");
for (int i=0;i<n;i++){
if (i==0){
head=new Node(scanner.nextInt());
}else {
Node current=new Node(scanner.nextInt());
current.next=head;
head=current;
}
}
return head;
}
//todo 尾插法创建链表
public static Node create_link2(int n){
size=n;
Node head=null;
Node tmp=null;
Scanner scanner = new Scanner(System.in);
System.out.println("输入链表data:");
for (int i = 0; i < n; i++) {
if (i==0){
head=new Node(scanner.nextInt());
head.next=null;
tmp=head;
}else{
Node current = new Node(scanner.nextInt());
tmp.next=current;
tmp=current;
current.next=null;
}
}
return head;
}
//todo 展示链表所有节点数据
public static void show_link(Node head){
Node tmp=head;
System.out.print("链表所有数据:");
while (tmp!=null){
System.out.print(tmp.data+" ");
tmp=tmp.next;
}
System.out.println();
}
//todo 获取index-1的节点数据
public static Node get_index(int index,Node head){
Node tmp=head;
for (int i =1; i < index-1; i++) {
tmp=tmp.next;
}
return tmp;
}
//todo 删除第index个节点
public static Node delete_index(int index,Node head){
if (index<=0 || index>size){
throw new IndexOutOfBoundsException("删除位置有误!");
}
if (index==1){ //删除头节点
head= head.next;
}else if (index==size){ //删除尾结点
Node pre_index = get_index(index, head);
pre_index.next=null;
}else { //删除中间节点
Node pre_index = get_index(index, head);
pre_index.next=pre_index.next.next;
}
size--;
return head;
}
//TODO 使用冒泡排序对链表进行排序
public static Node maopao_sort(Node head){
Node new_head=head;
for (int i=1;i<size;i++){
Node pre=null;
Node current=new_head;
Node N=current.next;
for (int j=0;j<size-i;j++){
if (current.data>N.data){
current.next=N.next;
N.next=current;
if (pre==null){
new_head=N;
}else{
pre.next=N;
}
pre=N;
N=current.next;
}else {
pre=current;
current=N;
N=N.next;
}
}
}
return new_head;
}
//todo 链表反转
public static Node reverse_link(Node head){
Node new_head=null;
Node tmp=head;
Node Next=head;
while (Next!=null){
Next=Next.next;
tmp.next=new_head;
new_head=tmp;
tmp=Next;
}
return new_head;
}
//todo 链表递归反转
public static Node recursion_reverse(Node head){
if (head.next==null)
return head;
Node new_head = recursion_reverse(head.next);
head.next.next=head;
head.next=null;
return new_head;
}
//链表节点对象
static class Node{
private int data; //数据域
private Node next; //指针域
public Node(int data){
this.data=data;
}
@Override
public String toString() {
return "Node{" +
"data=" + data +
", next=" + next +
'}';
}
}
}
面试:删除链表中与val值相等的所有节点:
//todo 删除链表中等于给定值val的所有节点
public static Node deleteByVal(Node head,int val){
Node tmp=head;
int index=0;
while (tmp!=null){
index++;
if (tmp.data==val){
delete_index(index,head);
index--;
}
tmp=tmp.next;
}
return head;
}
//todo 删除链表中等于给定值val的所有节点
public static Node removeVal(Node head,int val){
if (head==null){
return null;
}
head.next = removeVal(head.next, val);
return head.data==val?head.next:head;
}
递归解法详解图:
面试:二叉树创建、遍历
package algorithm_;
import java.util.*;
/**
* @author brett
* @date 2022-12-10 20:05:17
*/
public class BinaryTree {
public static void main(String[] args) {
LinkedList<Integer> list = new LinkedList<>(
Arrays.asList(8,7,6,null,null,5,null,null,9,4,1,null,null,null,3,10)
);
TreeNode root = createTree(list);
// System.out.println("前序遍历...");
// preOrder(root);
// System.out.println("中序遍历...");
// inOrder(root);
// System.out.println("非递归前序遍历...");
// preOrder2(root);
// System.out.println("非递归中序遍历...");
// inOrder2(root);
// System.out.println("后序遍历...");
// postOrder(root);
// System.out.println("非递归中序遍历...");
// postOrder2(root);
System.out.println("非递归层序遍历...");
levelOrder(root);
System.out.println("递归层序遍历...");
levelOrder2(Collections.singletonList(root));
}
//递归--创建二叉树
public static TreeNode createTree(LinkedList<Integer> list){
TreeNode node=null;
if (list==null || list.isEmpty()){
return null;
}
Integer data = list.removeFirst();
if (data!=null){
node=new TreeNode(data);
node.left=createTree(list);
node.right=createTree(list);
}
return node;
}
//前序遍历
public static void preOrder(TreeNode node){
if (node==null){
return;
}
System.out.println(node.data);
preOrder(node.left);
preOrder(node.right);
}
//中序遍历
public static void inOrder(TreeNode node){
if (node==null){
return;
}
inOrder(node.left);
System.out.println(node.data);
inOrder(node.right);
}
//后序遍历
public static void postOrder(TreeNode node){
if (node==null){
return;
}
postOrder(node.left);
postOrder(node.right);
System.out.println(node.data);
}
//非递归前序遍历
public static void preOrder2(TreeNode root){
Stack_Tree stackTree = new Stack_Tree(10);
TreeNode tmp=root;
while (tmp!=null || !stackTree.isEmpty()){
while (tmp!=null){
System.out.println(tmp.data);
stackTree.push(tmp);
tmp=tmp.left;
}
if (!stackTree.isEmpty()){
tmp=stackTree.pop();
tmp=tmp.right;
}
}
}
//非递归中序遍历
public static void inOrder2(TreeNode root){
Stack_Tree stackTree = new Stack_Tree(10);
TreeNode tmp=root;
while (tmp!=null || !stackTree.isEmpty()){
while (tmp!=null){
stackTree.push(tmp);
tmp=tmp.left;
}
if (!stackTree.isEmpty()){
tmp=stackTree.pop();
System.out.println(tmp.data);
tmp=tmp.right;
}
}
}
//非递归后序遍历
public static void postOrder2(TreeNode root){
Stack_Tree stackTree = new Stack_Tree(10);
TreeNode tmp=root; //当前访问的节点
TreeNode lastVisit=null; //标记上一次访问的节点
while (tmp!=null){
stackTree.push(tmp);
tmp=tmp.left;
}
while (!stackTree.isEmpty()) {
tmp=stackTree.pop();//弹出栈顶元素
//一个根节点被访问的前提是:无右子树或右子树已被访问过
if (tmp.right!=null && tmp.right!=lastVisit){
//根节点再次入栈
stackTree.push(tmp);
//进入右子树,且可以肯定右子树一定不为空
tmp=tmp.right;
while (tmp!=null){
//再走到左子树最左边
stackTree.push(tmp);
tmp=tmp.left;
}
}else {
//访问
System.out.println(tmp.data);
//修改最近被访问的节点
lastVisit = tmp;
}
}
}
//层序遍历
public static void levelOrder(TreeNode root){
Queue queue = new Queue(10);
queue.enQueue(root);
while (!queue.isEmpty()){
TreeNode tmp = queue.deQueue();
System.out.println(tmp.data);
if (tmp.left!=null){
queue.enQueue(tmp.left);
}
if (tmp.right!=null){
queue.enQueue(tmp.right);
}
}
}
//层序遍历递归实现
public static void levelOrder2(List<TreeNode> list){
if (list.size()==0)
return;
List<TreeNode> list2=new ArrayList<>();
for (TreeNode e:list) {
System.out.println(e.data);
if (e.left!=null){
list2.add(e.left);
}
if (e.right!=null){
list2.add(e.right);
}
}
levelOrder2(list2);
}
static class TreeNode{
//数据域
public int data;
//左子树
public TreeNode left;
//右子树
public TreeNode right;
public TreeNode(int data){
this.data=data;
}
}
static class Stack_Tree{
//数据域
public TreeNode[] arr;
//栈有效长度
public int size;
public Stack_Tree(int capacity){
arr=new TreeNode[capacity];
this.size=0;
}
//出栈
public TreeNode pop(){
if (this.size==0){
throw new IndexOutOfBoundsException("栈空");
}
TreeNode remove_data = this.arr[size - 1];
this.size--;
return remove_data;
}
//入栈
public void push(TreeNode node){
if (this.size==this.arr.length){
throw new IndexOutOfBoundsException("栈满");
}
this.arr[this.size]=node;
this.size++;
}
//判断栈是否为空
public boolean isEmpty(){
return this.size == 0;
}
}
static class Queue{
TreeNode[] arr; //存储数据
int front; //头指针
int rear; //尾指针
public Queue(int capacity){
arr=new TreeNode[capacity];
}
//入队操作
public void enQueue(TreeNode node){
//判断是否队满
if ((rear+1)%this.arr.length==front){
throw new IndexOutOfBoundsException("队列已经满了");
}
this.arr[rear]=node;
rear=(rear+1)%this.arr.length;
}
//出队操作
public TreeNode deQueue(){
//判断是否对空
if (rear==front){
throw new IndexOutOfBoundsException("队列已经空了");
}
TreeNode remove_data=this.arr[front];
front=(front+1)%this.arr.length;
return remove_data;
}
//输出队列中所有元素
public void output(){
for (int i=front;i!=rear;i=(i+1)%this.arr.length){
System.out.println(this.arr[i]);
}
}
//判断队空
public boolean isEmpty(){
return rear==front;
}
}
}