目录

  • 二分查找
  • 八大排序
    • 特点
    • 冒泡排序
    • 快速排序
    • 直接插入排序
    • 折半插入排序
    • 希尔排序
    • 简单选择排序
    • 堆排序
    • 二路归并排序
    • 基数排序
  • 二叉查找树(二叉排序树)
  • 哈希表(散列表)
    • 1.概念
    • 2.特点
    • 3.怎么处理散列冲突?
    • 4.散列表java代码实现
  • 拓扑排序
    • 1.基本思想:
  • 动态规划
    • 1.基本思想:
    • 2.应用——将动态规划拆分成三个子目标
  • 分治法
    • 基本概念
    • 基本思想和策略
    • 分治法的适用情况
    • 分治法的基本步骤
    • 依据分治法设计程序时的思维过程
  • 贪心算法
    • 基本概念
    • 贪心算法的基本思路
    • 贪心算法适用的问题
    • 贪心算法的实现框架
    • 贪心策略的选择
  • 字符串
  • 红黑树
  • 回溯法
    • 概念
    • 基本思想
    • 解题的一般步骤
  • 分支限界法
    • 基本描述
    • 分支限界法的一般过程
    • 回溯法和分支限界法的一些区别

二分查找

java二分查找代码

/*** 不使用递归的二分查找*title:commonBinarySearch*@param arr*@param key*@return 关键字位置*/
public static int commonBinarySearch(int[] arr,int key){int low = 0;int high = arr.length - 1;int middle = 0;            //定义middleif(key < arr[low] || key > arr[high] || low > high){return -1;               }while(low <= high){middle = (low + high) / 2;if(arr[middle] > key){//比关键字大则关键字在左区域high = middle - 1;}else if(arr[middle] < key){//比关键字小则关键字在右区域low = middle + 1;}else{return middle;}}return -1;        //最后仍然没有找到,则返回-1
}

python二分查找代码

def binary_search(lis, nun):left = 0right = len(lis) - 1while left <= right:   #循环条件mid = (left + right) // 2   #获取中间位置,数字的索引(序列前提是有序的)if num < lis[mid]:  #如果查询数字比中间数字小,那就去二分后的左边找,right = mid - 1   #来到左边后,需要将右变的边界换为mid-1elif num > lis[mid]:   #如果查询数字比中间数字大,那么去二分后的右边找left = mid + 1    #来到右边后,需要将左边的边界换为mid+1else:return mid  #如果查询数字刚好为中间值,返回该值得索引return -1  #如果循环结束,左边大于了右边,代表没有找到lis = [11, 32, 51, 21, 42, 9, 5, 6, 7, 8]print(lis)lis.sort()print(lis)while 1:num = int(input('输入要查找的数:'))res = binary_search(lis, num)print(res)if res == -1:print('未找到!')else:print('找到!')

八大排序

特点

①排序的平均时间复杂度?
②不稳定的排序有?
③排序的空间复杂度?
④其他特殊情况

①时间的复杂度:
o(log2n):快速排序,希尔排序,归并排序,堆排序(快些归队),
特殊的为基数排序O(d(n+rd)),其他都是O(n2)
②不稳定的排序:快速排序,希尔排序,简单选择排序,堆排序(快些选对)
③空间复杂度:
归并排序O(n),快速排序:o(log2n),基数排序:O(rd),其他都是O(1)
④其他特殊情况:
1.有序时:直接插入排序和冒泡排序时间复杂度为O(n),快排为O(n2)

冒泡排序

基本思想:从“第一位”开始,相邻比较,把最值放到最后。如此循环

如果有 n 个数据,那么只需要比较 n–1 轮。而且除了第一轮之外,每轮都不用全部比较。因为经过前面轮次的比较
,已经比较过的轮次已经找到该轮次中最大的数并浮到右边了,所以右边的数不用比较也知道是大的。所以是n-1-i

java实现代码:

public static int[] bubbleSortOnce(){int[] num = nums;for(int i=0; i<num.length ;i++){for(int j =1 ; j < num.length -i; j++){if(num[j] < num[j-1] ){int tmp = num[j];num[j] = num[j-1];num[j-1] = tmp;                    }}}

python实现代码:

def mp(sz):for i in range(0,len(sz)):for j in range(0,len(sz)-1):if sz[j]>sz[j+1]:temp=sz[j]sz[j]=sz[j+1]sz[j+1]=temp

快速排序

基本思想:

  • 从数列中挑出一个元素,称为 “基准”;
  • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区操作;
  • 递归地把小于基准值元素的子数列和大于基准值元素的子数列排序。

java实现代码:

void Function(int a[],int low,int high){int i=low,j=high,temp;if(low<high){temp=a[low];//把第一位作为基准while(i<j){while(i<j&&a[j]>=temp){j--;}if(i<j){a[i]=a[j];i++;}while(i<j&&temp>a[i]){i++;}if(i<j){a[j]=a[i];j--;}}a[i]=temp;Function(a,low,i-1);Function(a,i+1,high);}
}

python实现代码:

def kp(a,low,high):i=lowj=highif low<high:temp=a[low]while i<j:while i<j and a[j]>=temp:j=j-1if i<j:a[i]=a[j]i=i+1while i<j and temp>a[i]:i= i+1if i<j:a[j]=a[i]j=j-1a[i]=tempkp(a,low,i-1)kp(a,i+1,high)

直接插入排序

基本思想:每次从无序表中取出第一个元素,把它插入到有序表的合适位置,使有序表仍然有序

java实现代码:

void Function(int a[],int n){int i,j;int temp;for(i=1;i<n;i++){temp=a[i];//从后往前比较,大于就往后移for(j=i-1;j>=0&&a[j]>temp;j--){a[j+1]=a[j];}//最后一位空位为j,但有j--,所以再+1a[j+1]=temp;}
}

python实现代码:

def kp(a,n):for i in range(1,n):temp=a[i]j=i-1while j>=0 and a[j]>temp:a[j+1]=a[j]j=j-1a[j+1]=temp

折半插入排序

思想:和直接插入排序类似,区别是查找方法不同,折半插入排序是二分查找法查找插入的位置。


java实现代码:

public static int[] binarySort(int R[]){for (int i = 1; i < R.length; i++) {int temp = R[i];//暂存待插入元素//获取要插入的位置前一个元素的索引indexPreint indexPre = biSearchGetIndex(R, i - 1, temp);for (int j=i-1;j>indexPre;j--) {R[j+1]=R[j]; //元素后移}R[indexPre+1] = temp; //插入}return R;}public static int biSearchGetIndex(int R[],int last,int num ){int i,j;i = 0;j = last;while (j>=i){int temp = (i+j)/2;if(R[temp]>num){j=temp-1;}else {i=temp+1;}}return j;}

希尔排序

基本思想:基与直接插入排序,递减增量排序算法,是插入排序的一种更高效的改进版本.。

简单选择排序

基本思想:选出最小值,放在"第一位",然后"第一位"向后推移。如此循环。

java实现代码:

void Function(int a[],int n){int temp,min;int i,j;for(i=0;i<n;i++){min=i;for(j=i+1;j<n;j++){if(a[j]<a[min]){min=j;}}temp=a[min];a[min]=a[i];a[i]=temp;}}

python实现代码:

def ks(sz):for i in range(0,len(sz)-1):min=i;for j in range(i+1,len(sz)): #注意:range(0,10)为0~9if sz[min]>sz[j]:min=j;temp = sz[i]sz[i] = sz[min]sz[min] = temp

堆排序

基本思想:

  • 将无需序列构层序遍历顺序建成一个堆,根据升序降序需求选择大顶堆或小顶堆;

  • 将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;

  • 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

任一个非叶节点的值都不大于其左右孩子的值,若父亲大孩子小,则为大顶堆,若父亲小孩子大,则为小顶堆。

原始序列对应二叉树层序遍历

调整步骤:
①从第一个非叶子结点开始,从右至左,从下至上(即按照层序序号递减)调整。
②将当前结点的值与孩子结点比较,如果存在>a的孩子,则从中选出最大的一个和a交换。当a来到下一层时,重复上述操作,直至a的孩子结点值都小于a为止

例子:假设给定无序序列结构如下

①从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。

找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。

这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。

此时,我们就将一个无需序列构造成了一个大顶堆。

②将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。

将堆顶元素9和末尾元素4进行交换

重新调整结构,使其继续满足堆定义


再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.

后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序

二路归并排序

基本思想:归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;

即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

我们举个例子,将两个有序数组合并成一个有序数组,如下图。

归并排序的步骤,我们给定一个无序数组{9, 2, 6, 3, 5, 7, 10, 11}

不断递归直至每个分组中都只有一个元素,即每个分组都是一个有序数组,不过只有一个元素一定是有序的。

java实现代码:

ElemType *B=(ElemType *)malloc((n+1)*sizeof(ElemType))//辅助数组Bvoid Merge(ElemType A[],int low,int mid,int high){for(int k=low;k<=high;k++){B[k]=A[k];       //将A中所有元素复制到B中}for(i=low,j=mid+1,k=i;i<=mid&&j<=high;k++){if(B[i]<=B[j])A[k]=B[i++];elseA[k]=B[j++];}while(i<=mid)  A[k++]=B[i++];     //若第一个表未检测完,复制while(j<=high) A[k++]=B[j++];     //若第一个表未检测完,复制
}void MergeSort(ElemType A[],int low,int high){if(low<high){int mid=(low+high)/2;  //从中间划分两个子序列MergeSort(A,low,mid);   //对左侧子序列进行递归排序MergeSort(A,mid+1,high); //对右侧子序列进行递归排序Merge(A,low,mid,high);    //归并}
}

python实现代码:

def Merge(nums, low, mid, high):res = []i = lowj = mid+1while i<=mid and j<=high:if nums[i]<=nums[j]:res.append(nums[i])i += 1else:res.append(nums[j])j += 1while i<=mid:res.append(nums[i])i += 1while j<=high:res.append(nums[j])j += 1nums[low:high+1] = res[:]
def MergeSort(nums, low, high):if low<high:mid = (low+high)/2MergeSort(nums, low, mid)MergeSort(nums, mid+1, high)Merge(nums, low, mid, high)

基数排序

例子:
原始序列:278 109 063 930 589 184 505 269 008 083(过程)
有最高位优先,有最低位优先。

二叉查找树(二叉排序树)

1.概念:
(1)若左子树不空,则左子树上所有节点的值均小于它的根节点的值;
(2)若右子树不空,则右子树上所有节点的值均大于它的根节点的值;
(3)左、右子树也分别为二叉排序树(递归);
(4)没有值相等的节点。

2.优点:数组的搜索比较方便,可以直接用下标,但删除或者插入某些元素就比较麻烦;链表与之相反,删除和插入元素很快,但查找很慢;二叉排序树就既有链表的好处,也有数组的好处。

3.二叉排序树的建立:
序列:50,38,30,45,40,48,70,60,75,80

4.二叉排序树的查找,插入,删除思路:

  • 查找,插入:找到位置直接操作
  • 删除有三种情况:
    1.被删除结点P为叶子结点:直接删除
    2.被删除结点P只有左子树或只有右子树:将被删除结点P的左子树或右子树接成其双亲结点的左/右子树
    3.被删除结点P有左子树和右子树:用被删除结点P左子树中最大的值(即最右端结点S)代替被删除结点P,并释放S(最右端结点S)

5.java代码实现:

/**二叉排序树中的查找算法*/public boolean searchBST(int key){Node current = root;while (current != null){//等于当前值,返回trueif(key == current.getValue())return true;//小于当前值,进入左节点else if (key < current.getValue())current = current.getLeft();//进入右节点elsecurrent = current.getRight();}//没找到结果返回falsereturn false;}/**二叉排序树中插入结点*/public boolean insertBST(int newKey){Node p = root;Node parent = null;//记录插入结点位置(prev为要插入结点的父节点)Node prev = null;//在树中寻找插入位置while (p != null){parent = p;prev = p;if(newKey > p.getValue())p = p.getRight();else if (newKey < p.getValue())p = p.getLeft();//键值为newKey的结点已在树中,不再插入elsereturn false;}//若为空树,键值为newKey的结点为树根if (root == null)root = new Node(newKey,parent);//作为左结点插入else if (newKey < prev.getValue())prev.setLeft(new Node(newKey,parent));//作为右结点插入elseprev.setRight(new Node(newKey,parent));return true;}/**删除结点,有三种情况:* 1.被删除结点P为叶子结点:直接删除* 2.被删除结点P只有左子树或只有右子树:将被删除结点P的左子树或右子树接成其双亲结点的左/右子树* 3.被删除结点P有左子树和右子树:* 用被删除结点P左子树中最大的值(即最右端结点S)代替被删除结点P,并删除左子树中最大值(最右端结点S)*/public boolean deleteBST(int key){return deleteBST(root,key);}public boolean deleteBST(Node node,int key){if (node == null)return false;else {if (key == node.getValue())return deleteNode(node);else if (key < node.getValue())return deleteBST(node.getLeft(),key);elsereturn deleteBST(node.getRight(),key);}}private boolean deleteNode(Node node) {Node temp = null;//被删除结点为叶子结点,直接删除if(node.getLeft() == null && node.getRight() == null){//被删除结点是根结点if(node == root)root = null;else{if(node.getValue() == node.getParent().getLeft().getValue())node.getParent().setLeft(null);elsenode.getParent().setRight(null);}}//被删除结点P只有左子树或只有右子树,将被删除结点P的左子树或右子树接成其双亲结点的左/右子树else if (node.getLeft() == null){if(node.getValue() == node.getParent().getLeft().getValue())node.getParent().setLeft(node.getRight());elsenode.getParent().setRight(node.getRight());}else if (node.getRight() == null){if(node.getValue() == node.getParent().getLeft().getValue())node.getParent().setLeft(node.getLeft());elsenode.getParent().setRight(node.getLeft());}//被删除结点P有左子树和右子树,//用被删除结点P左子树中最大的值(即最右端结点S)代替被删除结点P,并删除左子树中最大值(最右端结点S)else {temp = node;Node s = node;/**在左子树中获取最右端结点S*/s = s.getLeft();while(s.getRight() != null){temp = s;s = s.getRight();}//S替代Pnode.setValue(s.getValue());//删除Sif(temp != node){temp.setRight(s.getLeft());}else{temp.setLeft(s.getLeft());}}return true;}

哈希表(散列表)

1.概念

散列表是根据键(Key)而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。

2.特点

  • 优点:不论哈希表中有多少数据,查找、插入、删除(有时包括删除)只需要接近常量的时间即0(1)的时间级。
  • 缺点:它是基于数组的,数组创建后难于扩展;一个关键字可能对应多个散列地址;需要查找一个范围时,效果不好

3.怎么处理散列冲突?

散列冲突:不同的关键字经过散列函数的计算得到了相同的散列地址。

好的散列函数=计算简单+分布均匀(计算得到的散列地址分布均匀)

① 开放定址法
一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。 发生冲突,另寻他处

我们把这种解决冲突的方法称为线性探测法。 我们在解决冲突的时候,还会碰到比如说一个数 48, 它所在的位置已经被占用,它只能往后延,但是又与后面的冲突 ,本来两个数一点关系都没有,但是发生冲突,这种现象称为堆积, 堆积的出现使得我们需要不断处理冲突,即48要不断向后延。存入以及查找的效率都会大大降低。

优化方案:改进 d i = 12 , -12 , 22, -22 …… q2 , -q2 (q <= m/2),这样就等于可以是双向寻找可能的位置了,

增加平方运算的目的是为了不让关键字都聚集在某一个区域,我们称这样的方法为二次探测法

改进方案: 在冲突时,对于位移量 d 1 采用随机函数计算得到,我们称之为随机探测法。(伪随机)


② 再散列函数法:

准备多个散列函数,比如前面说的除留余数、折叠、平方取中全部用上,每当发生散列地址冲突时,就换一个散列函数计算,这种方法使得关键字不产生聚焦,当然也会增加计算的时间 不断尝试。

③ 链地址法

将所有关键字为同义词的记录存储在一个单链表中,我们称这种表为同义词子表,不是同义词放在多个位置了,而是把他们集中起来, 在散列表里只存储所有同义词子表的头指针。

链地址法对于可能会造成很多冲突的散列函数来说,提供了绝不会出现找不到地址的保障。当然,这也会增加查找时需要遍历单链表的性能损耗。

4.散列表java代码实现

节点数据结构

package com.billJiang.hashtable;/*** Created by billJiang on 2016/11/30.* hashtable 节点 可能发生碰撞*/
public class Entry<T> {int key;T item;Entry<T> next;public Entry(int key,T item,Entry<T> next){this.key=key;this.item=item;this.next=next;}
}

实现代码

package com.billJiang.hashtable;import java.util.Arrays;/*** Created by billJiang on 2016/11/30.*/
public class HashTable<T> {private static final int INITIAL_SIZE=3;private static final float LOAD_FACTOR=0.75f;private  Entry<T>[] table;private int size=0;private int use=0;public HashTable() {table = new Entry[INITIAL_SIZE];}public void put(int key,T item){int index=hash(key);if(table[index]==null){table[index]=new Entry(-1,null,null);}Entry e=table[index];//未存过值if(e.next==null){Entry entry=new Entry(key,item,null);e.next=entry;size++;use++;if(use>=table.length*LOAD_FACTOR){resize();}}else{//已经存在值,替换for(e=e.next;e!=null;e=e.next){if(e.key==key){e.item=item;return;}}//追加Entry temp=table[index].next;Entry entry=new Entry(key,item,temp);table[index].next=entry;size++;}}public void remove(int key){int index=hash(key);Entry e=table[index];Entry pre=table[index];for(e=e.next;e!=null;e=e.next){if(e.key==key){pre.next=e.next;size--;//TODO 24页缺少以下两行代码if (pre.key == -1 && e.next == null)use--;break;}pre=e;}}public T get(int key){int index=hash(key);Entry e=table[index];for(e=e.next;e!=null;e=e.next){if(e.key==key){return (T) e.item;}}return null;}public int size(){return this.size;}public int getLength(){return table.length;}private int hash(int key){return key%table.length;}private void resize(){Entry[] oldTable=table;table=new Entry[table.length*2];use=0;for(int i=0;i<oldTable.length;i++){if(oldTable[i]!=null&&oldTable[i].next!=null){Entry e= oldTable[i];Entry next=e.next;while(e.next!=null){int index=hash(next.key);if(table[index]==null){table[index]=new Entry(-1,null,null);use++;}Entry temp=table[index].next;table[index].next=new Entry(next.key,next.item,temp);e=next;}}}}
}

拓扑排序

1.基本思想:

  • 从有向图中选择一个没有前驱(入度为0)的顶点输出
  • 删除上面的顶点,并且删除从该顶点发出的所有边
  • 重复上述两步,直到剩余的图中不存在没有前驱的顶点为止

动态规划

1.基本思想:

取决于该问题是否能用动态规划解决的是这些”小问题“会不会被重复调用。

如果一个问题满足以下两点,能用动态规划解决:

  • 问题的答案依赖于问题的规模​,也就是问题的所有答案构成了一个数列。
  • 大规模问题的答案可以由小规模问题的答案递推得到,也就是​ [公式] 的值可以由​ [公式] 中的个别求得。

2.应用——将动态规划拆分成三个子目标

  • 建立状态转移方程:这一步是最难的,大部分人都被卡在这里。这一步没太多的规律可说,只需抓住一个思维:当做已经知道​ [公式] ~​ [公式] 的值,然后想办法利用它们求得 [公式] ​。
  • 缓存并复用以往结果:这一步不难,但是很重要。如果没有合适地处理,很有可能就是指数和线性时间复杂度的区别。
  • 按顺序从小往大算:这里的“小”和“大”对应的是问题的规模

分治法

基本概念

把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)……

任何一个可以用计算机求解的问题所需的计算时间都与其规模有关。问题的规模越小,越容易直接求解,解题所需的计算时间也越少。例如,对于n个元素的排序问题,当n=1时,不需任何计算。n=2时,只要作一次比较即可排好序。n=3时只要作3次比较即可,…。而当n较大时,问题就不那么容易处理了。要想直接解决一个规模较大的问题,有时是相当困难的。

基本思想和策略

分治法的设计思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。

分治策略是:对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。这种算法设计策略叫做分治法。

如果原问题可分割成k个子问题,1<k≤n,且这些子问题都可解并可利用这些子问题的解求出原问题的解,那么这种分治法就是可行的。由分治法产生的子问题往往是原问题的较小模式,这就为使用递归技术提供了方便。在这种情况下,反复应用分治手段,可以使子问题与原问题类型一致而其规模却不断缩小,最终使子问题缩小到很容易直接求出其解。这自然导致递归过程的产生。

分治与递归像一对孪生兄弟,经常同时应用在算法设计之中,并由此产生许多高效算法。

分治法的适用情况

分治法所能解决的问题一般具有以下几个特征:

  • 该问题的规模缩小到一定的程度就可以容易地解决
  • 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。
  • 利用该问题分解出的子问题的解可以合并为该问题的解;
  • 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。

第一条特征是绝大多数问题都可以满足的,因为问题的计算复杂性一般是随着问题规模的增加而增加;
第二条特征是应用分治法的前提它也是大多数问题可以满足的,此特征反映了递归思想的应用;、
第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑用贪心法或动态规划法。
第四条特征涉及到分治法的效率,如果各子问题是不独立的则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然可用分治法,但一般用动态规划法较好。

分治法的基本步骤

分治法在每一层递归上都有三个步骤:

  • step1 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
  • step2 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题
  • step3 合并:将各个子问题的解合并为原问题的解。

依据分治法设计程序时的思维过程

实际上就是类似于数学归纳法,找到解决本问题的求解方程公式,然后根据方程公式设计递归程序。
1、一定是先找到最小问题规模时的求解方法
2、然后考虑随着问题规模增大时的求解方法
3、找到求解的递归函数式后(各种规模或因子),设计递归程序即可。

贪心算法

基本概念

所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。所以对所采用的贪心策略一定要仔细分析其是否满足无后效性。

贪心算法的基本思路

1.建立数学模型来描述问题。
2.把求解的问题分成若干个子问题。
3.对每一子问题求解,得到子问题的局部最优解。
4.把子问题的解局部最优解合成原来解问题的一个解。

贪心算法适用的问题

贪心策略适用的前提是:局部最优策略能导致产生全局最优解。实际上,贪心算法适用的情况很少。一般,对一个问题分析是否适用于贪心算法,可以先选择该问题下的几个实际数据进行分析,就可做出判断。

贪心算法的实现框架

从问题的某一初始解出发;
while (能朝给定总目标前进一步)
{
利用可行的决策,求出可行解的一个解元素;
}
由所有解元素组合成问题的一个可行解;

贪心策略的选择

因为用贪心算法只能通过解局部最优解的策略来达到全局最优解,因此,一定要注意判断问题是否适合采用贪心算法策略,找到的解是否一定是问题的最优解。

字符串

红黑树

回溯法

概念

回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。

回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。

许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。

基本思想

在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。

若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。
而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。

解题的一般步骤

(1)针对所给问题,确定问题的解空间: 首先应明确定义问题的解空间,问题的解空间应至少包含问题的一个(最优)解。
(2)确定结点的扩展搜索规则
(3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。

分支限界法

基本描述

类似于回溯法,也是一种在问题的解空间树T上搜索问题解的算法。但在一般情况下,分支限界法与回溯法的求解目标不同。回溯法的求解目标是找出T中满足约束条件的所有解,而分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解。

(1)分支搜索算法

所谓“分支”就是采用广度优先的策略,依次搜索E-结点的所有分支,也就是所有相邻结点,抛弃不满足约束条件的结点,其余结点加入活结点表。然后从表中选择一个结点作为下一个E-结点,继续搜索。

选择下一个E-结点的方式不同,则会有几种不同的分支搜索方式。

  • FIFO搜索
  • LIFO搜索
  • 优先队列式搜索

(2)分支限界搜索算法

分支限界法的一般过程

由于求解目标不同,导致分支限界法与回溯法在解空间树T上的搜索方式也不相同。回溯法以深度优先的方式搜索解空间树T,而分支限界法则以广度优先或以最小耗费优先的方式搜索解空间树T。

分支限界法的搜索策略是:在扩展结点处,先生成其所有的儿子结点(分支),然后再从当前的活结点表中选择下一个扩展对点。为了有效地选择下一扩展结点,以加速搜索的进程,在每一活结点处,计算一个函数值(限界),并根据这些已计算出的函数值,从当前活结点表中选择一个最有利的结点作为扩展结点,使搜索朝着解空间树上有最优解的分支推进,以便尽快地找出一个最优解。

分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。问题的解空间树是表示问题解空间的一棵有序树,常见的有子集树和排列树。在搜索问题的解空间树时,分支限界法与回溯法对当前扩展结点所使用的扩展方式不同。在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,那些导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所求的解或活结点表为空时为止。

回溯法和分支限界法的一些区别

有一些问题其实无论用回溯法还是分支限界法都可以得到很好的解决,但是另外一些则不然。也许我们需要具体一些的分析——到底何时使用分支限界而何时使用回溯呢?

回溯法和分支限界法的一些区别:

  • 方法对解空间树的搜索方式
  • 存储结点的常用数据结构
  • 结点存储特性常用应用

回溯法深度优先搜索堆栈活结点的所有可行子结点被遍历后才被从栈中弹出找出满足约束条件的所有解。

分支限界法广度优先或最小消耗优先搜索队列、优先队列每个结点只有一次成为活结点的机会找出满足约束条件的一个解或特定意义下的最优解。

数据结构与算法基本思想相关推荐

  1. 数据结构与算法——动态规划思想解题三角形问题

    动态规划的基本思想 动态规划解题的时候将问题分解为几个不同的阶段(把原始问题分解为不同的子问题),自底向上计算.每次决策都依赖于当前的状态.我们可以将不同阶段的不同状态存储在一个二维数组中. 从这个二 ...

  2. 【数据结构与算法】【算法思想】 A *搜索算法

    算法解析 这是一个非常典型的搜索问题. 人物的起点就是他当下所在的位置,终点就是鼠标点击的位置. 我们需要在地图中,找一条从起点到终点的路径. 这条路径要绕过地图中所有障碍物,并且看起来要是一种非常聪 ...

  3. 【数据结构与算法】【算法思想】【算法应用】【排序查找搜索】并行

    算法的目的就是为了提高代码执行的效率.当算法无法再继续优化的情况下,需要借助并行计算的处理思想对算法进行改造. 并行排序 假设要给大小为 8GB 的数据进行排序,最常用的是三种排序算法,归并排序.快速 ...

  4. 【数据结构与算法】【算法思想】【MySQL数据库索引】B+树

    B+树特点 考虑因素 支持按照区间来查找数据 磁盘 IO 操作 N叉树 树的高度就等于每次查询数据时磁盘 IO 操作的次数 在选择 m 大小的时候,要尽量让每个节点的大小等于一个页的大小.读取一个节点 ...

  5. 【数据结构与算法】【算法思想】回溯算法

    贪心算法 回溯算法 分治算法 动态规划 回溯算法思想应用广泛,除了用来指导深度优先搜索这种经典算法设计之外,还可以用在如正则表达式匹配,编译原理中的语法分析等. 除此之外,很多经典的数学问题都可以用回 ...

  6. 【数据结构与算法】【算法思想】分治算法

    贪心算法 回溯算法 分治算法 动态规划 MapReduce本质就是分治算法,是Google大数据处理的三驾马车之一,另外两个是GFS和Bigtable.它在倒排索引,PageRank计算,网页分析等搜 ...

  7. 【数据结构与算法】【算法思想】贪心算法

    贪心算法 回溯算法 分治算法 动态规划 四种基本的算法思想:贪心算法,分治算法,回溯算法,动态规划,他们不是具体算法,常用来指导我们设计具体的算法和编码等. 一:贪心算法有很多经典应用 霍夫曼编码(H ...

  8. python思想读后感_数据结构与算法:Python语言描述读后感1000字

    <数据结构与算法:Python语言描述>是一本由裘宗燕著作,机械工业出版社出版的平装图书,本书定价:CNY 45.00,页数:343,特精心从网络上整理的一些读者的读后感,希望对大家能有帮 ...

  9. 【数据结构与算法】【算法思想】Dijkstra算法

    图的两种搜索算法,深度优先搜素和广度优先搜索.这两种算法主要是针对无权图的搜索算法.针对有权图,也就是图中的每条边都有一个权重,该如何计算两点之间的最短路径?最短路径算法(Shortest Path ...

最新文章

  1. Spring Boot 核心知识,深入剖析!
  2. oracle中偏移,怎么对相同的坐标点偏移?
  3. layui 在springboot2.x 时,页面展示不了layui的问题
  4. OraOLEDbpus.dll找不到指定的模块的解决办法
  5. ctf的php,CTF中常见的PHP漏洞
  6. matlab读取data格式,ReadData3D 各种格式图像的读取,包括医学 效果很好 matlab 272万源代码下载- www.pudn.com...
  7. 我的HEVC码流分析工具MFC小笔记:树形控件使用及窗口缩放
  8. bzoj 2502: 清理雪道(有下界的最小流)
  9. BZOJ4570: [Scoi2016]妖怪
  10. 桌面支持--dcc打印机设置注意
  11. 输入法android版,享受流畅手机输入 百度手机输入法Android版试用
  12. 商务网站建设与维护【5】
  13. ffmpeg中的pcm格式
  14. cattee翻译_钻机词汇中英翻译
  15. PyCharm的Requirement already satisfied 解决方法
  16. android studio画板手指位置和画线位置有差_iOS概念画板5版本导出技巧
  17. 写给编程初学者的一篇文章,该如何学习编程?我的编程学习之路
  18. 【北交所周报】继北证50指数后,北交所推出融资融券交易细则;新股慧为智能上市当日收涨22.13%,远航精密上市即破发;...
  19. 数字图像处理课程实习——傅里叶变换与频域滤波
  20. android 联想云盘,附文:联想云盘安装_联想 小新Air 12 LTE版_笔记本评测-中关村在线...

热门文章

  1. 博士申请 | 上海财经大学语言智能实验室招收2022年秋季入学博士生
  2. 直播预告 | 小米人工智能部崔世起:小爱同学全双工技术实践
  3. MixPath:基于权重共享的神经网络搜索统一方法
  4. 广西大学计算机技术复试题库,2018年广西大学计算机与电子信息学院408计算机学科专业基础综合之计算机操作系统考研基础五套测试题...
  5. 【Java代码】Lamda表达式将List对象中的Map对象的key全部转化为大写或者小写【去除外层循环:可用于Map对象中的key全部转化为大写或者小写】
  6. linux declare大小写,关于linux:将用户输入转换为大写
  7. 你是否真正理解了泛型、通配符、类型擦除
  8. 「中间件系列二」redis缓存
  9. Privatization of Roads in Treeland
  10. android小闹钟程序,Android实现闹钟小程序.pdf