递归算法
1.什么是递归? 递归是将问题拆分成比自己规模小的同等问题,然后用递归函数来分解问题。
递归的经典问题
- 求阶乘
- 汉诺塔问题
- 求二叉树的深度
- 求二叉树是否是平衡二叉树
- 排列算法
- 二分查找
汉诺塔问题
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)分解,求解,合并
分治的经典问题
- 二分搜索
- 大整数乘法
- rassen矩阵乘法
- 棋盘覆盖
- 合并排序
- 快速排序
动态规划
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);
}