最近又去看了算法方面的知识,虽然在有的编程语言中开发者已经将最优秀的算法封装到了相应的方法中,可以直接调用,但是衡量我们是否有发展潜力的还是数据结构和算法,有位计算机大神说过:程序=算法+数据结构,掌握好算法才能进好的公司,有好的发展潜力。
算法
最初的算法——冒泡排序
冒泡排序作为大多数大学第一个学习的排序算法,带领我们进入了算法领域。
package com.interview.sort;
public class BubbleSort {
//冒泡排序
public static void sort(int arr[]){
int n = arr.length-1;
for (int i = 0 ; i < n-1 ; i ++ ){
for( int j = 0 ; j <= n-i-1 ; j ++){
if (arr[j]>arr[j+1]){
swap(arr,j,j+1);
}
}
}
}
public static void swap(int[] arr,int i,int j){
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
public static void main(String[] args) {
int[] a = {8,4,9,2,3,7,12,10};
sort(a);
for (int i=0 ; i < a.length ;i++){
System.out.println(a[i]);
}
System.out.println(a.length);
}
}
具体思路:冒泡排序通过遍历整个数组,从当前元素开始遍历,如果当前元素大于后面元素,就交换位置,通过一遍一遍的交换,就能将大的元素全部放到后面。
时间复杂度:最好O(n) 最坏O( n^2 ) 平均 O(n^2)
稳定性:稳定
数组近乎有序情况下最好的算法——插入排序
插入排序在数组元素近乎有序的情况下排序的效率要高于快速排序。
package com.interview.sort;
public class InsertSort {
public static void sort(int[] arr) {
int n = arr.length;
//插入排序,从下标为1开始,比较当前和前面的元素,如果小于的话,就交换/赋值
for (int i = 1 ; i < n ; i++){
//交换/赋值的是相邻的元素
for (int j = i ; j > 0 ; j--){
if (arr[j] < arr[j-1])
swap(arr,j-1,j);
else
break;
}
}
for (int i=0 ; i < n ;i++){
System.out.println(arr[i]);
}
}
public static void swap(int[] arr,int i,int j){
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
public static void main(String[] args) {
int[] a = {9,4,2,4,3,12,10};
sort(a);
}
}
算法思路:通过遍历整个数组(从下标为1的元素开始遍历),将该元素同前面的元素比较,如果小于,则不断交换到合适的地方(同冒泡有一点类似)。
时间复杂度 最好O(n) 最坏O(n^2)
稳定性:稳定
插入排序的优化——希尔排序
希尔排序是插入排序的优化,通过将整个数组分为一个一个的组,在组内进行插入排序,之后不断减小组的间隔,最后达到完全排序的结果。
在这里插入代码片
算法思路:将整个数组分为按照不同的间隔分组,每次对该组进行插入排序,之后缩小间隔,再次插入排序,直到间隔为0,完全排序。
时间复杂度:最好小于O(n^2)并且大于o(NlogN)
稳定性:稳定
最容易理解的排序算法——选择排序
将最小的放在第一位,第二小的放在第二位,依次类推。
package com.interview.sort;
public class SelectSort {
public static void sort(int[] a){
int length = a.length;
int flag = 0;
//选择排序不断找当前最小的,放到合适位置
for (int i=0 ; i < length ; i++){
int min = i;
for (int j = i ; j < length ; j++){
if (a[j] < a[min])
min = j;
}
swap(a,i,min);
}
for (int i=0 ; i < a.length ;i++){
System.out.println(a[i]);
}
}
public static void swap(int[] arr,int i,int j){
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
public static void main(String[] args) {
int[] a = {4,9,2,3,4,10,12};
sort(a);
}
}
算法思路:遍历整个数组,依次找到最小的,将其放在应该在的位置。
时间复杂度:最好最坏都是O(N^2)
稳定性:不稳定
二分递归的排序算法——归并排序
归并排序使用了分而治之的思想,将一个数组分为一个一个的小的数组,之后将每个小的数组归并起来并排序,依次将每个小的数组归并起来,直到完全排序。
package com.interview.sort;
import java.util.Arrays;
public class MergeSort {
public static void sort(int[] arr){
int r = arr.length-1;
int l = 0;
mergeSort(arr,l,r);
}
public static void mergeSort(int[] arr, int l, int r){
if (l>=r)
return;
int mid = (l+r)/2;
mergeSort(arr,l,mid);
mergeSort(arr,mid+1,r);
merge(arr,l,mid,r);
}
private static void merge(int[] arr, int l, int mid, int r) {
// //归并思路
/**
* 分而治之,先将数组不断的二分,直到分成一个一个的元素,此时直接return
* 之后执行归并方法,先将该数组中的元素放到一个临时的数组中
* 比较这个数组的前半部分和后半部分,依次放入之前传入的数组中
*/
int[] aux = Arrays.copyOfRange(arr, l, r+1);
int i = l,j = mid+1;//记录开头的元素,通过比较两个数组开头的元素
//并且将指针不断的后移
for (int k = l ; k <= r ; k ++ ){
if ( i > mid ){
arr[k] = aux[j-l];
j++;
}
else if (j > r){
arr[k] = aux[i-l];
i++;
}
else if (aux[i-l] < aux[j-l]){
arr[k] = aux[i-l];
i++;
}else {
arr[k] = aux[j-l];
j++;
}
}
}
public static void main(String[] args) {
int[] a = {4,9,2,3,7,12,10};
sort(a);
for (int i=0 ; i < a.length ;i++){
System.out.println(a[i]);
}
}
}
算法思路:先将整个数组分成一个一个小的数组,再使用归并将其不断的归并排序起来。
时间复杂度:O(NlogN)
稳定性,不稳定
目前最好的排序算法——快速排序
一提到排序,我们都会想到快速排序,因为它太出名了,并且他的时间复杂度是比较快的。
package com.interview.sort;
public class QuickSort {
public static void sort(int[] arr) {
int l = 0;
int r = arr.length-1;
quickSort(arr,l,r);
}
private static void quickSort(int[] arr, int l, int r) {
//快速排序思路
//找到一个中间值,小的放在他前面,大的放在后面,递归调用
if(l>=r)
return;
int flag = doQuickSort(arr,l,r);
quickSort(arr,l,flag-1);
quickSort(arr,flag+1,r);
}
private static int doQuickSort(int[] arr, int l, int r) {
int v = arr[l];
int j = l;
for (int i = l+1 ; i <= r ; i ++ ){
if (arr[i] < v){
swap(arr,j+1,i);
j++;
//此处将小于V的元素和大于V的第一个交换,之后将J再次指向小于V的最后一个
//此处J是最后一个小于V的数组下标
}
}
swap(arr,l,j);
return j;
}
public static void swap(int[] arr,int i,int j){
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
public static void main(String[] args) {
int[] a = {4,9,6,3,7,12,10};
sort(a);
for (int i=0 ; i < a.length ;i++){
System.out.println(a[i]);
}
}
}
算思路:先找一个中间点,将小于中间节点的放在中间点的前面,大于中间节点的放在节点后面,依次递归,直到每一个元素被排好,归并排序是不断分成小的数组,最后合并,快速排序是每次对大的数组排序完成之后将其分成小的,再对小的进行排序,直到结束。
时间复杂度:O(NlogN)
稳定性:不稳定
要求优先级情况下使用的排序算法——堆排序
要理解堆排序,首先要理解什么是堆,堆其实就是一棵完全二叉树,并且通过从上而下是降序还是升序确定是最大堆还是最小堆。
- 堆可以使用数组进行存放,最好从下标为1开始存放。
- shiftup,使用insert方法插入一个节点时,将该节点同父亲节点相比较(下标为N/2),如果当前节点大于父节点就交换,直到N>1时停止。
- shiftdown,将根节点取出之后,将数组中最后一个节点放在首节点位置上,并且将其同2N和2N+1相比较,如果该节点小于任意子节点,就将该节点同子节点中最大的那个交换(为了保证交换后还能符合最大堆的性质);如果当前节点小于其中一个子节点并且大于另一个子节点,就和大的那个交换,直到完全大于任意子节点。
时间复杂度:O(logN)
稳定性:不稳定
二分查找法
二分查找法使用了分而治之的思想,不断的二分,直到找到或者没有找到。
package com.interview.find;
public class BinarySearch {
public static int find(int[] arr, int l, int r, int target) {
int mid = (l + r) / 2;
if (arr[mid] == target)
return mid;
else if (target < arr[mid])
return find(arr, l, mid - 1, target);
else if (target > arr[mid])
return find(arr, mid + 1, r, target);
else
return -1;
}
public static int find1(int[] arr,int target){
int l = 0;
int r = arr.length-1;
while( l <= r){
int mid = l + (r-l)/2;
if (arr[mid] == target)
return mid;
else if (target < arr[mid])
r = mid - 1;
else if (target > arr[mid])
l = mid + 1;
}
return -1;
}
public static void main(String[] args) {
int arr[] = {1,3,5,6,8,9,15,18};
int mid = BinarySearch.find1(arr,8);
System.out.println(mid);
}
}
时间复杂度:O(logN)
二叉搜索树
二叉搜索树作为天然可以二分查找的数据结构,一直都是非常重要的,并且在早期的数据库索引中也曾被广泛使用,二叉搜索树具有 左子树<父亲节点<右子树的特点,使用递归以及迭代可以很好的查询数据。
以下为Java中二叉搜索树的实现。
功能分别有 插入、查询节点是否存在、查询节点信息、查询最大值、查询最小值、删除最大值、删除最小值、前序、中序、后序 (深度优先)、层序遍历(广度优先),删除任意值。
package com.interview.Tree;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
public class BST {
public class Node{
private int key;
private String value;
Node left;
Node right;
public Node(int key, String value) {
this.key = key;
this.value = value;
this.left = null;
this.right = null;
}
public Node(Node node) {
this.key = node.key;
this.value = node.value;
this.left = node.left;
this.right = node.right;
}
}
private Node root;
private int count;
BST(){
root = null;
count = 0;
}
public int size(){
return count;
}
public boolean isEmpty(){
return count==0;
}
//插入新的节点
/**
* 插入节点
* @param key
* @param value
* @return
*/
public void insert(int key,String value){
root = insert(root ,key , value);
}
/**
* 向以Node为节点插入节点,返回根节点
* @param node
* @param key
* @param value
* @return
*/
private Node insert(Node node , int key , String value){
if (node == null ){
count++;
return new Node(key,value);
}
if (key == node.key)
node.value = value;
else if (key < node.key)
node.left = insert(node.left,key,value);
else
node.right = insert(node.right,key,value);
return node;
}
/**
* 查看是否包含该值
* @param key
* @return
*/
public boolean contain(int key){
return contain(root,key);
}
/**
* 查找是否包含
* @param node
* @param key
* @return
*/
private boolean contain(Node node ,int key){
if (node == null)
return false;
if (key == node.key)
return true;
else if (key<node.key)
return contain(node.left,key);
else
return contain(node.right,key);
}
public Node search(int key){
return search(root,key);
}
private Node search(Node node, int key){
if (node == null)
return null;
if (node.key == key)
return node;
else if (key < node.key)
return search(node.left,key);
else
return search(node.right,key);
}
/**
* 前序
* @param node
*/
public void before(Node node){
if (node == null)
return;
System.out.print(node.key+" ");
before(node.left);
before(node.right);
}
/**
* 后序
* @param node
*/
public void older(Node node) {
if (node == null)
return;
older(node.left);
older(node.right);
System.out.print(node.key+" ");
}
/**
* 中序
* @param node
*/
public void between(Node node) {
if (node == null)
return;
between(node.left);
System.out.print(node.key+" ");
between(node.right);
}
/**
* 层序
* @param node
*/
public void sequence(Node node){
//若根节点为空则返回
if(node == null){
return;
}
//根节点不为空则如下分析:
//1.将根节点放入队列,开始遍历二叉树
//2.输出队首元素,
//3.若左子树不为空,则入队列
//4.若右子树不为空,则入队列
Queue<Node> queue = new LinkedBlockingQueue<>();
queue.add(node);
Node tempNode;
while (!queue.isEmpty()){
tempNode = queue.remove();
System.out.print(tempNode.key+" ");
if (tempNode.left != null)
queue.add(tempNode.left);
if (tempNode.right != null)
queue.add(tempNode.right);
}
}
public Node minimum(){
return minimum(root);
}
private Node minimum(Node node){
if (node.left == null)
return node;
return minimum(node.left);
}
public Node maximum(){
return maximum(root);
}
private Node maximum(Node node){
if (node.right == null)
return node;
return maximum(node.right);
}
public void removeMin(){
if ( root!= null)
root = removeMin(root);
}
/**
* 删除最小节点,并且返回二叉树的根节点
* @param node
* @return
*/
private Node removeMin(Node node){
if (node.left == null){
count--;
return node.right;
}
node.left = removeMin(node.left);
return node;
}
public void removeMax(){
if (root != null)
root = removeMax(root);
}
/**
* 删除最大值并且返回树的根节点
* @param node
* @return
*/
private Node removeMax(Node node){
if (node.right == null){
count -- ;
return node.left;
}
node.right = removeMax(node.right);
return node;
}
public void remove(int key){
root = remove(root,key);
}
private Node remove(Node node,int key){
if (node == null)
return null;
if (key < node.key){
node.left = remove(node.left,key);
return node;
}else if (key > node.key){
node.right = remove(node.right,key);
return node;
}else {
if (node.left == null){
count--;
return node.right;
}
if (node.right == null){
count--;
return node.left;
}
Node successor = new Node(minimum(node.right));
count++;
successor.right = removeMin(node.right);
successor.left = node.left;
count--;
return successor;
}
}
public static void main(String[] args) {
int[] arr = {8,1,3,6,7,12,32,0,-1,-2,2};
BST bst = new BST();
for ( int i = 0 ; i < arr.length ; i ++ ){
bst.insert(arr[i],i+"ss");
}
System.out.println("前序遍历");
bst.before(bst.root);
System.out.println();
System.out.println("后序遍历");
bst.older(bst.root);
System.out.println();
System.out.println("中序遍历");
bst.between(bst.root);
System.out.println();
System.out.println("层序遍历");
bst.sequence(bst.root);
System.out.println();
System.out.println("共有 "+bst.count+" 个节点");
System.out.println();
System.out.println("最大值为:"+bst.maximum().key+" 最小值为:"+bst.minimum().key);
System.out.println();
System.out.println("删除最小值后的前序遍历");
bst.removeMin();
bst.before(bst.root);
System.out.println("删除1之后的前序遍历");
bst.remove(1);
bst.before(bst.root);
}
}
二叉搜索树缺点是如果插入一个纯有序的数组就会退化成链表,此时可以打乱顺序再进行插入,二叉搜索树+平衡二叉树可以构建一个比较厉害的数据结构:红黑树。这就要大家自己去理解了,数据结构这一块需要多多刷题,不能光背代码,多刷牛客以及LeetCode才能融会贯通。
图论准备中。