二分搜索(Binary Search)
时间复杂度 :
假使总共有n个元素,那么二分后每次查找的区间大小就是n,n/2,n/4,…,n/2^k(接下来操作元素的剩余个数),其中k就是循环的次数。
最坏的情况是K次二分之后,每个区间的大小为1,找到想要的元素
令n/2^k=1,
可得k=log2n,(是以2为底,n的对数),所以时间复杂度可以表示O()=O(logn).
int BinarySearch(int[] list, int target){
int left = 0;
int right = list.length-1;
while(left <= right)
{
int mid = (right + left)/2;
if(list[mid] == target)
return mid;
elseif(list[mid] < target)
left = mid + 1;
elseif(list[mid] > target)
right = mid - 1;
}
return -1;
}
分治思想
基本特征:
1.问题缩小到一定规模容易解决
2.分解成的子问题是相同种类的子问题,即该问题具有最优子结构性质
3.分解而成的小问题在解决后要可以合并
4.子问题是相互独立的,即子问题之间没有公共的子问题
经典hanoi塔
void hanoi(n, a, b, c){
if(n == 1)
{
cout<<a<<"->"<<c<<endl;
}
else
{
hanoi(n-1, a, c, b);
cout<<a<<"->"<<c<<endl;
hanoi(n-1, b, a, c);
}
}
main()
{
int n = 3;
hanoi(n, a, b, c);
}
宽度优先搜索Breadth First Search
BFS使用队列(queue)来实施算法过程,队列(queue)有着先进先出FIFO(First Input First Output)的特性,BFS操作步骤如下:
1、把起始点放入queue;
2、重复下述2步骤,直到queue为空为止:
1) 从queue中取出队列头的点;
2) 找出与此点邻接的且尚未遍历的点,进行标记,然后全部放入queue中。
数组表示:查找每个顶点的邻接点所需时间为O(n2),n为顶点数,算法的时间复杂度为O(n2)
# 利用队列实现树的广度优先遍历
def level_queue(root):
if root is None:
return
my_queue=[]
node = root
my_queue.append(node)
while my_queue:
node=my_queue.pop(0)
print(node.elem)
if node.lchild is not None:
my_queue.append(node.lchild)
if node.rchild is not None:
my_queue.append(node.rchild)
深度优先搜索Depth First Search
DFS的实现方式相比于BFS应该说大同小异,只是把queue换成了stack而已,stack具有后进先出LIFO(Last Input First Output)的特性,DFS的操作步骤如下:
1、把起始点放入stack;
2、重复下述3步骤,直到stack为空为止:
- 从stack中访问栈顶的点;
- 找出与此点邻接的且尚未遍历的点,进行标记,然后全部放入stack中;
- 如果此点没有尚未遍历的邻接点,则将此点从stack中弹出。
数组表示:查找所有顶点的所有邻接点所需时间为O(n2),n为顶点数,算法时间复杂度为O(n2)
# 利用递归实现深度优先
def depth_tree(tree_node):
if tree_node is not None:
print(tree_node._data)
if tree_node._left is not None:
return depth_tree(tree_node._left)
if tree_node._reght is not None:
return depth_tree(tree_node._reght)
快速排序
快速排序基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
package test;
public class test {
public static void main(String[] args) {
int [] array={4,8,6,9,7,1,3,2,5};
QuickSort(array,0,array.length-1);
for(int i=0;i<array.length;i++)
{
System.out.print(array[i]+" ");
}
}
public static void QuickSort(int [] array,int start,int end)
{
int low=start;
int high=end;
int key=array[low];
while(low<high)
{
while(low<high&&array[high]>=key)
high--;//当end结点值大于基准值时,向前移动,直到找到小于基准值的值
if(low<high)
{
array[low]=array[high];
low++;
}
while(low<high&&array[low]<=key)low++;
if(low<high)
{
array[high]=array[low];
high--;
}
}
array[low]=key;//此时start和end 已经指向同一元素
if(low-1>start)QuickSort(array,start,low-1);
if(high+1<end)QuickSort(array,high+1,end);
}
}
随机选取数据
假设有一个集合A(a_1, ..., a_n),对于数m,0<=m<=n,如何从集合A中等概率地选取m个元素
通过计算古典概率公式可以得到,每个元素被选取的概率为m/n。如果集合A里面的元素本来就具有随机性,每个元素在各个位置上出现的概率相等,并且只在A上选取一次数据,那么直接返回A的前面m个元素就可以了,或者可以采取每隔k个元素取一个等类似的方法。
(1)假设集合A中的元素在各个位置上不具有随机性,比如已经按照某种方式排序了,那么我们可以便利集合A中的每一个元素a_i,0<=n根据一定的概率选取ai。
设m'为还需要从A中选取的元素个数,n'为元素a_i及其右边的元素个数,也即n'=(n-i+1)。那么选取元素a_i的概率为m'/n'。
我们简单计算一下前面两个元素(2<=m<=n)各被选中的概率。
1)设p(a_i=1)表示a_i被选中的概率。显而易见,p(a_1=1)=m/n,p(a_1=0)为(n-m)/n;
2)第二个元素被选中的概率为
p(a_2=1) = p(a_2=1,a_1=1)+p(a_2=1,a_1=0)
= p(a_1=1)*p(a_2=1|a_1=1)+p(a_1=0)*p(a_2=1|a_1=0)
= m/n * (m-1)/(n-1) + (n-m)/n*m/(n-1)
= m/n
c++实现上述算法
template<class T>
bool getRand(const vector vecData, int m, verctor& vecRand)
{
int32_t nSize = vecData.size()
if(nSize < m || m < 0)
return false;
vecRand.clear();
vecRand.reserve(m);
for(int32_t i = 0, isize = nSize; i < isize; i++){
float fRand = frand();
if(fRand <=(float)(m)/nSize){
vecRand.push_back(vecData[i]);
m--;
}
nSize --;
}
return true;
}
A*算法
A*算法原理:
open列表:记录所有被考虑来寻找最短路径的方块
closed列表:记录下不会再被考虑的方块
G值:从开始点A到当前方块的移动量
H值:从当前方块到终点的移动量估算值
F值(和值):每个方块的和值 F=G+H
重复以下步骤来找到最短路径:
1.将方块添加到open列表中,该列表有最小的和值。且将这个方块成为S吧
2.将S从open列表移除,然后添加S到closed列表中。
3.对于与S相邻的每一块可通行的方块T:
-如果T在closed列表中:不管它。
-如果T不在open列表中:添加它然后计算出它的和值。
-如果T已经在open列表中:当我们使用当前生成的路径达到那里时,检查F和值是否更小。如果是,更新它的和值和它的前继。
[openList add:originalSquare]; // start by adding the original position to the open list
do {
currentSquare = [openList squareWithLowestFScore]; // Get the square with the lowest F score
[closedList add:currentSquare]; // add the current square to the closed list
[openList remove:currentSquare]; // remove it to the open list
if ([closedList contains:destinationSquare]) { // if we added the destination to the closed list, we've found a path
// PATH FOUND
break; // break the loop
}
adjacentSquares = [currentSquare walkableAdjacentSquares]; // Retrieve all its walkable adjacent squares
foreach (aSquare in adjacentSquares) {
if ([closedList contains:aSquare]) { // if this adjacent square is already in the closed list ignore it
continue; // Go to the next adjacent square
}
if (![openList contains:aSquare]) { // if its not in the open list
// compute its score, set the parent
[openList add:aSquare]; // and add it to the open list
} else { // if its already in the open list
// test if using the current G score make the aSquare F score lower, if yes update the parent because it means its a better path
}
}
} while(![openList isEmpty]); // Continue until there is no more available square in the open list (which means there is no path)