经典算法

1,107 阅读8分钟

递归算法

1.什么是递归? 递归是将问题拆分成比自己规模小的同等问题,然后用递归函数来分解问题。

递归的经典问题

  1. 求阶乘
  2. 汉诺塔问题
  3. 求二叉树的深度
  4. 求二叉树是否是平衡二叉树
  5. 排列算法
  6. 二分查找
汉诺塔问题
void hano(char a,char b,char c,int n){
        if (n > 0) {
            hano(a, c, b, n-1);
            System.out.println(a+"->"+c);
            hano(b, a, c, n-1);
        }

分治算法

1.什么是分治算法? 将一个规模为N的问题分解成几个子问题,找到求出这几个子问题的解法后,再找到合适的方法,把它们组合成求整个问题的解法 2. 分治法解题的一般步骤:

(1)分解,求解,合并

分治的经典问题

  1. 二分搜索
  2. 大整数乘法
  3. rassen矩阵乘法
  4. 棋盘覆盖
  5. 合并排序
  6. 快速排序

动态规划

1.什么是动态规划? 是求解决策过程(decision process)最优化的数学方法。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解

经典算法题

1. 插入排序

有顺序的一组数组,让插入后。仍然是有序的 算法时间复杂度 O(n2)--[n 的平方]

 public  int[] insert(int[] arr, int num) {
       //循环数组
        int len = arr.length;
        int[] a = new int[len + 1];
        for (int i = 0; i < arr.length; i++) {
            //换位
            if (num > arr[i]) {
                a[i] = arr[i];
            } 
            else {    //从后往前 
                for (int j = arr.length; j > i; j--) {
                    a[j] = arr[j - 1];
                }
                a[i] = num;
                break;  // 一定要加 
            }
        }
        if (a[len] == 0) {
            a[len] = num;
        }
        return a;
    }

2. 选择排序

在要排序的一组数中,选出最小的一个数与第一个位置的数交换;然后在剩下的数当中再找最 小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。 算法复杂度 O(n2)-- [n 的平方]

public static void sort(Comparable[] data){
        int len=data.length;     //记录数组的长度
        for(int i=0;i<len;i++){    //for循环数组
            //记录当前位置
            int position=i;
            for(int j=i+1;j<len;j++){
                //如果前一位大
                if(data[position].compareTo(data[j])>0){
                    position=j;     //循环最小位
                }
            }
            // 交换最小位和第i位的位置
            Comparable d=data[i];
            data[i]=data[position];
            data[position]=d;
        }

3. 冒泡算法

一次比较两个元素,如果他们的顺序错误就把他们交换过来。 冒泡排序是稳定的。算法时间复杂度 O(n2)--[n 的平方]

public void sort(int[] a){
        int temp = 0;
        for(int i = 0;i<a.length;i++){
            for(int j=i;j<a.length;j++){
                if(a[i]>a[j]){
                    int temp = a[i];
                    a[i] = a[j];
                    a[j] = temp;
                }
            }
        }
    }

4. 快速排序

使左边的都小于,右边都大于 第一次:从后往前找比基数小的 第二次 :从前往后找到一个比基数大的 快速排序是不稳定的。最理想情况算法时间复杂度 O(nlog2n),最坏 O(n2)

 //arr[] 数组,low数组第一个元素索引,high为最后一个元素的索引
  public static void sort(int arr[], int left, int right) {

    if(left < right){
            int key = arr[left];
            int low = left;
            int high = right;
            while(low < high){
                // 从后往前找到一个比基数小的
                while(low < high && arr[high] > key){
                    high--;
                }
                arr[low] = arr[high];
               
                while(low < high && arr[low] < key){
                    low++;
                }
                arr[high] = arr[low];
            }
            arr[low] = key;
            sort(arr,left,low-1);
            sort(arr,low+1,right);
        }
    }

5.归并排序

空间复杂度为 O(n) 比较操作的次数介于(nlogn) / 2 和 nlogn - n + 1,赋值操作的次数是(2nlogn)。 归并排序比较占用内存,但却是一种效率高且稳定的算法。 即先使每个子序列有序,再使子序列段间有序

   void merge(int[] sourceArr, int[] tempArr, int startIndex, int midIndex, int endIndex) {

        int i = startIndex;
        int k = startIndex;
        int j= midIndex+1;
        while (i !=midIndex+1 && j!= endIndex+1)     //中间数大于s,小于等于end
            if (sourceArr[i] > sourceArr[j])
                tempArr[k++] = sourceArr[i--];
            else
                tempArr[k++] = sourceArr[j--];
        while (i != midIndex+1) 
            tempArr[k++] = sourceArr[i++];
        
        while (j != endIndex + 1) 
            tempArr[k++] = sourceArr[j--];
        
        for (i = startIndex; i <= endIndex; i++) 
            sourceArr[i] = tempArr[i];
    }

    void MergeSort(int sourceArr[], int tempArr[], int startIndex, int endIndex) {
        int midIndex;
        if (startIndex < endIndex) {
            midIndex = (startIndex + endIndex) / 2;
            MergeSort(sourceArr, tempArr, startIndex, midIndex);
            MergeSort(sourceArr, tempArr, midIndex + 1, endIndex);
            merge(sourceArr, tempArr, startIndex, midIndex, endIndex);
        }
    }

6. 求二叉树中的节点个数

递归解法: (1)如果二叉树为空,节点个数为 0 (2)如果二叉树不为空,二叉树节点个数 = 左子树节点个数 + 右子树节点个数 + 1 1是gen根j根=结点
参考代码如下:

int GetNodeNum(BinaryTreeNode * pRoot) {
    if(pRoot == NULL) // 递归出口 
    return 0;
return GetNodeNum(pRoot->m_pLeft) + GetNodeNum(pRoot->m_pRight) + 1; }

7.求二叉树的深度

递归解法: (1)如果二叉树为空,二叉树的深度为 0 (2)如果二叉树不为空,二叉树的深度 = max(左子树深度, 右子树深度) + 1

参考代码如下:

int GetDepth(BinaryTreeNode * pRoot){
    if(pRoot == NULL) // 递归出口 return 0;
    int left = getTreeDept(root.leftChild);
    int right = getTreeDept(root.rightChild);

   if(left>=right)
       return left+1;
   else
       return right+1;
}

8.前序遍历,中序遍历,后序遍历

前序遍历递归解法: (1)如果二叉树为空,空操作 (2)如果二叉树不为空,访问根节点,前序遍历左子树,前序遍历右子树 参考代码如下:

void PreOrderTraverse(BinaryTreeNode * pRoot) {
if(pRoot == NULL)
    return;
Visit(pRoot);    // 访问根节点 

PreOrderTraverse(pRoot->m_pLeft); // 前序遍历左子树

PreOrderTraverse(pRoot->m_pRight); // 前序遍历右子树
}

中序遍历递归解法 (1)如果二叉树为空,空操作。 (2)如果二叉树不为空,中序遍历左子树,访问根节点,中序遍历右子树

void InOrderTraverse(BinaryTreeNode * pRoot) {
if(pRoot == NULL) return;
InOrderTraverse(pRoot->m_pLeft); // 中序遍历左子树 
Visit(pRoot); // 访问根节点
InOrderTraverse(pRoot->m_pRight); // 中序遍历右子树
}

后序遍历递归解法 (1)如果二叉树为空,空操作 (2)如果二叉树不为空,后序遍历左子树,后序遍历右子树,访问根节点

void PostOrderTraverse(BinaryTreeNode * pRoot) {
if(pRoot == NULL) 
return;

PostOrderTraverse(pRoot->m_pLeft); // 后序遍历左子树 

PostOrderTraverse(pRoot->m_pRight); // 后序遍历右子树 Visit(pRoot); // 访问根节点
}

9.平衡二叉树

任意节点的左右子树深度之差不超过 1.

 //如果等于空的话,就不考虑了。
        if(root == null){     //为空。当然是平衡的
            return true;
        }
        int left = getTreeDept(root.leftChild);
        int right=getTreeDept(root.rightChild);
        int diff=left-right;
        if(diff >1|| diff<-1){    //深度之差大于1返回false
            return false;
        }
        //左右树同时进行判断平衡
        return is_balanced(root.rightChild) && is_balanced(root.leftChild);
    }

10.已知两个单链表 pHead1 和 pHead2 各自有序,把它们 合并成一个链表依然有序

这个类似归并排序。尤其注意两个链表都为空,和其中一个为空时的情况。只需要 O(1)的空间。 时间复杂度为 O(max(len1, len2))。参考代码如下: 递归

ListNode MergeSortedList(ListNode head1 , ListNode  head2) {
if(head1 == NULL)
    return head2;
if(head2 == NULL)
    return head1;
    
ListNode newHead = NULL;
if(head1.m_nKey < head2.m_nKey){
         newHead = head1;
         newHead.next = MergeSortedList(head1.next, head2);
}else{
         newRoot = head2;
         newRoot.next = MergeSortedList(head1, head2.next); 
}
 return newRoot;
ListNode * MergeSortedList(ListNode * pHead1, ListNode * pHead2) {
    pHeadMerged = pHead1;
    if(pHead1 == NULL)
        return pHead2;
    if(pHead2 == NULL)
        return pHead1;
    ListNode * pHeadMerged = NULL;
    if(pHead1->m_nKey < pHead2->m_nKey){
        pHeadMerged = pHead1;
        HeadMerged->m_pNext = NULL;
        pHead1 = pHead1->m_pNext;
    else{
        pHeadMerged = pHead2;
        pHeadMerged->m_pNext = NULL;
        pHead2 = pHead2->m_pNext;
    }
    ListNode * pTemp = pHeadMerged;
    while(pHead1 != NULL && pHead2 != NULL){
        if(pHead1->m_nKey < pHead2->m_nKey){
            pTemp->m_pNext = pHead1;
            pHead1 = pHead1->m_pNext;
            pTemp = pTemp->m_pNext;
            pTemp->m_pNext = NULL;        
        }
        else{
            pTemp->m_pNext = pHead2;
            pHead2 = pHead2->m_pNext;
            pTemp = pTemp->m_pNext;
            pTemp->m_pNext = NULL;
        }
    }
 }
if(pHead1 != NULL)
    pTemp->m_pNext = pHead1;
else if(pHead2 != NULL)
    pTemp->m_pNext = pHead2;
    return pHeadMerged;

11.反转单链表

先将current指向prev,再将pnext指向current,最后将头结点指向pnext。

// 反转单链表
 LinkList current, p;
    if (L == NULL){
        return NULL;
    }
    current = L->next;
    while (current->next != NULL)
    {
        //p 就相当于前面的 pnext
        p = current->next;
        current->next = p->next;
        p->next = list->next;
        list->next = p;
    }
    return L;
}

12.链表是否有环

一个指针一次走两步,一个指针一次走一步,如果有环,两个 指针肯定会在环中相遇.时间复杂度为 O(n)。

boolean isRing(LinkNode root) {
        //初始化不是环
        LinkNode fast = root;
        LinkNode slow = root;    //起点相同

        while (fast != null && fast.next != null) {    //当前节点有,下一节点也有
            fast = fast.next.next;    //fast变成下一节点slow
            if (fast.equals(slow)) {
                break;
            }
        }
        //长度为偶数时,fast为空
        if(fast== null || fast.next==null){
            return false;
        }else{
            return true;
        }
        

13. 堆栈就是栈,仅允许在表的 表头(又名栈顶) 进行插入和删除运算,另一端称为表尾(栈底)

栈的基本操作有push(添加到堆栈),pop(从堆栈删除),peek(检测栈顶元素且不删除)。

public class 自定义堆栈 {
    private Deque<Integer> queue = new LinkedList<Integer>();  
    
    // push存入元素  
    public void push(Integer element) {  
        queue.addFirst(element);  
    }  
    
    // pop取出元素  
    public Integer pop() {  
        return queue.removeFirst();  
    }  
    /
    / peek获取元素,但是并不取出元素  
    public Integer peek() {  
        return queue.getFirst();  
    }  
  
    public String toString() {  
        return queue.toString();  
    }  
}  

14. 二分法:二分查找又称折半查找

int binary_search(int[] array,int des){
      //定义初识最小,最大索引
        int low=0;
        int high = array.length-1;     //作为下标
        //确保不会越界
        while((low<=high) && (low<array.length-1)&& (high<array.length-1)){
            int middle=(low+high)/2;        //中间值
            if(des==(low+high)){      //给的这个值刚好等于中间值
                return middle;
            }else if(des<array[middle]){    //往下走
                high = middle-1;
            }else{
                low = middle +1;
            }
            //在左半边
        }
        //若没有返回-1
        return -1;
    }

15.数组连续子集和

int getSum(int[] array){
    if(array==null|| array.length<1){
        System.out.println("list error");
        }
    int maxSofar=0;
    int Ending=0;
    for(int i=0;i<array.length;i++){
        maxSofar=Math.max(maxSofar+array[i],0);
        Ending = Math.max(maxSofar,Ending);
    }
        return Ending;
    }

16.最长递归增长长度

int getIncreateLength(int[] array){
        int[] len=new int[array.length];
        for(int i =0;i<array.length;i++){
            len[i]=1;
            for(int j=0;j<i;j++){
                if(array[j]<array[i] && len[j]>(len[i]-1)){
                    len[i]=len[j]+1;
                }
            }
        }
        int length=len.length-1;
        return len[length];         //数组最后一个的值
    } 

17.矩阵走法

int ways(int x,int y){
        if(x==1 || y==1){
            return 1;
        }
        return ways(x-1,y)+ways(x,y-1);
    }

相关算法参考:https://github.com/yangshuting1/leetcode