数据结构_排序算法总结
作者丨fredal
https://www.jianshu.com/p/28d0f65aa6a1
所有内部排序算法的一个总结表格
简单选择排序
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
最差:O(n2)
最优:O(n2)
平均:O(n2)
public void selectSort(int[] a){ for(int i=0;i<a.length-1;i++){ int min=i;//最小元素所在位置 for(int j=i+1;j<a.length;j++){ if(a[j]<a[min]) min=j; } {int temp=a[min];a[min]=a[i];a[i]=temp;}//交换元素,把最小元素放在头部 } }
冒泡排序
冒泡排序算法的运作如下:
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
最差:O(n2)
最优:O(n)
平均:O(n2)
public static void bubbleSort(int[] a){ for(int i=0;i<a.length-1;i++){ for(int j=0;j<a.length-i-1;j++){ if(a[j]>a[j+1]) {int temp=a[j];a[j]=a[j+1];a[j+1]=temp;}//交换元素位置 保证大的在后面 } } }
插入排序
一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:
从第一个元素开始,该元素可以认为已经被排序
取出下一个元素,在已经排序的元素序列中从后向前扫描
如果该元素(已排序)大于新元素,将该元素移到下一位置
重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
将新元素插入到该位置后
重复步骤2~5
最差:O(n2)
最优:O(n)
平均:O(n2)
直接插入排序:
public static void insertSort(int[] a){ for(int i=1;i<a.length;i++){ for(int j=0;j<=i;j++){ if(a[j]>=a[i]){//找到了第一个比自己大的元素,插入到该元素之前 int temp=a[i]; for(int k=i-1;k>=j;k--) a[k+1]=a[k];//每个元素向后移动一格 a[j]=temp; break; } } } }
折半插入排序:
就是寻找第一个比自己大的元素的时候用折半查找进行优化:
public static void halfInsertSort(int[] a){ for(int i=1;i<a.length;i++){ int low=0; int high=i; while(low<=high){ int half=(low+high)/2; if(a[half]<a[i]){//插入点在高半区 low=half+1; }else{//插入点在低半区 high=half-1; } } //low或者high指向的元素就是第一个比自己大的元素 插入到之前 int temp=a[i]; for(int k=i-1;k>=low;k--) a[k+1]=a[k];//每个元素向后移动一格 a[low]=temp; } }
快速排序
快速排序使用分治法策略来把一个序列(list)分为两个子序列(sub-lists)。
步骤为:
从数列中挑出一个元素,称为"基准"(pivot)
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
最差:O(n2)
最优:O(n log n)
平均:O(n log n)
//这里left取0,right取a.length-1 public static void quickSort(int[] a,int left,int right){ if(left<right){//递归出口条件 int i=left;//左指针 int j=right;//右指针 int x=a[left];//选择第一个元素作为标尺 while(i<j){ while(i<j && a[j]>=x) j--;//从右向左找第一个小于x的数 if(i<j) a[i++]=a[j]; while(i<j && a[i]<x) i++;//从左向右找第一个大于等于x的数 if(i<j) a[j--]=a[i]; } a[i]=x;//插入标尺 quickSort(a,left,i-1);//递归左边 quickSort(a, i+1, right);//递归右边 } }
当前选取第一个元素作为标尺是比较不好的,一种安全的做法是随记选取,但是也不好.我们一般采用三数中值分割法,就是使用左端,右端,中心位置上的三个元素的中位数作为标尺进行排序.
public static void quickSortS(int[] a,int left,int right){ if(left<right){//递归出口条件 int i=left;//左指针 int j=right;//右指针 int center=(left+right)/2; //三数中值分割法选取标尺 if((a[left]<=a[center]&&a[center]<=a[right])||(a[right]<=a[center]&&a[center]<=a[left])){ int tmp=a[left]; a[left]=a[center]; a[center]=tmp;//记得交换噢 }else if((a[left]<=a[right]&&a[right]<=a[center])||(a[center]<=a[right]&&a[right]<=a[left])){ int tmp=a[left]; a[left]=a[right]; a[right]=tmp; } int x=a[left];//标尺 while(i<j){ while(i<j && a[j]>=x) j--;//从右向左找第一个小于x的数 if(i<j) a[i++]=a[j]; while(i<j && a[i]<x) i++;//从左向右找第一个大于等于x的数 if(i<j) a[j--]=a[i]; } a[i]=x;//插入标尺 quickSortS(a,left,i-1);//递归左边 quickSortS(a, i+1, right);//递归右边 } }
希尔排序
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法。
根据步长进行分组,对每一组进行插入排序.
最差:O(因步长各异,最好的n log2 n)
最优:O(n)
平均:O(因步长各异)
public static void shellSort(int[] a){ int igap=a.length;//初始化步长,第一次步长为数组长度的一半 for(int gap=igap/2;gap>0;gap/=2){//步长 for(int i=0;i<gap;i++){//共做步长次插入排序 for(int j=i+gap;j<a.length;j+=gap){//直接插入排序算法 for(int k=i;k<=j;k++){ if(a[k]>=a[j]){//找到了第一个比自己大的元素,插入到该元素之前 int temp=a[j]; for(int p=j-1;p>=k;p--) a[p+1]=a[p];//每个元素向后移动一格 a[k]=temp; break; } } } } } }
归并排序
原理如下(假设序列共有n个元素):
将序列每相邻两个数字进行归并操作,形成floor(n/2)个序列,排序后每个序列包含两个元素
将上述序列再次归并,形成floor(n/4)个序列,每个序列包含四个元素
重复步骤2,直到所有元素排序完毕
最差:O(n logn)
最优:O(n)
平均:O(n log n)
public static void mergeSort(int[] a,int first,int last){ if(first<last){ int[] temp=new int[a.length]; int middle=(first+last)/2; mergeSort(a, first, middle);//递归左边 mergeSort(a, middle+1, last);//递归右边 //将两个有序数列合并 int i=first; int j=middle+1; int k=0; while(i<=middle&&j<=last){ if(a[i]<=a[j]){ temp[k++]=a[i++]; }else{ temp[k++]=a[j++]; } } while(i<=middle){ temp[k++]=a[i++]; } while(j<=last){ temp[k++]=a[j++]; } //将排好的temp重新赋给a for(i=0;i<k;i++){ a[first+i]=temp[i]; } } }
树形选择排序
树形选择排序(Tree Selection Sort),又称锦标赛排序(Tournament Sort),是一种按照锦标赛思想进行选择排序的方法。
首先对n个记录的关键字进行两两比较,然后在其中[n/2](向上取整)个较小者之间再进行两两比较,如此重复,直至选出最小关键字的记录为止。
这个过程可以用一颗有n个叶子节点的完全二叉树表示,每个非终端节点都是左右孩子中的较小值,这样根节点就是所有叶子节点中的最小值了.把这个值输出后,将叶子节点中的最小值改为"最大值",然后从该叶子节点开始,和其左右兄弟进行比较,修改从叶子节点到根节点的路径上各节点的值,则根节点的值为次小值.同理,可从小到大排出所有值.
虽然说树,但是通常由数组实现,父节点i的左子节点在位置(2i+1);父节点i的右子节点在位置(2i+2);子节点i的父节点在位置floor((i-1)/2)
时间复杂度为O(n log n)
缺点是辅助存储空间太多,并且与"最大值"进行了多余的比较.
public static int[] TreeSelectSort(int[] data){ int dlong=data.length; int tlong=2*dlong-1; int low=0; int[] tree=new int[tlong]; int[] ndata=new int[dlong];
for(int i=0;i<dlong;i++){ tree[tlong-i-1]=data[i]; }
for(int i=tlong-1;i>0;i-=2){ tree[(i-1)/2]=(tree[i]<tree[i-1]?tree[i]:tree[i-1]); }
int minIndex; while(low<dlong){ int min=tree[0]; ndata[low++]=min; minIndex=tlong-1; //找到最小值 while(tree[minIndex]!=min){ minIndex--; } tree[minIndex]=Integer.MAX_VALUE; //找到其兄弟节点 while(minIndex>0){//有父节点 if(minIndex%2==0){//是右节点 tree[(minIndex-1)/2]=(tree[minIndex]<tree[minIndex-1]?tree[minIndex]:tree[minIndex-1]); minIndex=(minIndex-1)/2; }else{//是左节点 tree[minIndex/2]=(tree[minIndex]<tree[minIndex+1]?tree[minIndex]:tree[minIndex+1]); minIndex=minIndex/2; } } } return ndata;
}
堆排序
堆排序是对树形选择排序的一种优化,建立在"二叉堆"这种数据结构上,二叉堆属于完全二叉树,除此之外还需满足所有父节点都要大于或小于左右子树.
父节点大于左右孩子的叫最大二叉堆,父节点小于左右孩子的叫最小二叉堆.
以最大二叉堆为基础进行排序,就是先构建成一个最大堆,接着取出根节点,然后将剩下的数组元素在建成一个最大二叉堆,再取出根节点,如此循环直到所有元素都被取光.
和树形选择排序一样也是由数组实现,参考上一节.
最坏,最优和平均的时间复杂度都是nlogn.
public static void heapSort(int[] a){ //建立最大堆 int size=a.length; for(int i=(size-1-1)/2;i>=0;i--){ maxHeap(i,a,size); }
for(int i=a.length-1;i>0;i--){ //将根节点上的最大值不断与最后一个交换,并将最后一个筛选出来,指向最后的指针向前推进 int temp=a[i]; a[i]=a[0]; a[0]=temp; size--; //保证根节点最大特性,其他节点都已经保持了 maxHeap(0, a,size); } }
//保持堆的最大特性 private static void maxHeap(int i,int[] a,int size) { int left=2*i+1; int right=2*i+2; int largest=i; //分别与左右子树比较,取最大值 if(left<=size-1&&a[left]>a[i]){ largest=left; } if(right<=size-1&&a[right]>a[largest]){ largest=right; } if(largest!=i){ //交换根节点与最大值 int temp=a[i]; a[i]=a[largest]; a[largest]=temp; //递归 maxHeap(largest, a,size); }
}
桶排序
桶排序(Bucket Sort)的原理很简单,它是将数组分到有限数量的桶子里。
假设待排序的数组a中共有N个整数,并且已知数组a中数据的范围[0, MAX)。在桶排序时,创建容量为MAX的桶数组r,并将桶数组元素都初始化为0;将容量为MAX的桶数组中的每一个单元都看作一个"桶"。
在排序时,逐个遍历数组a,将数组a的值,作为"桶数组r"的下标。当a中数据被读取时,就将桶的值加1。例如,读取到数组a[3]=5,则将r[5]的值+1。
桶排序的时间复杂度为O(n+k),k为取值范围,在特殊情况下提供了排序的下界.
public static void bucketSort(int[] a,int max){ int[] buckets; if(a==null||max<1){ return; } buckets=new int[max];//创建一个容量为max的数组 并将其数据都初始化为0
//计数 for(int i=0;i<a.length;i++){ buckets[a[i]]++; } //排序 for(int i=0,j=0;i<max;i++){ while((buckets[i]--)>0){ a[j++]=i; } }
buckets=null; }
基数排序(多关键字排序)
基数排序已经不再是一种常规的排序方式,它更多地像一种排序方法的应用,基数排序必须依赖于另外的排序方法。基数排序的总体思路就是将待排序数据拆分成多个关键字进行排序,也就是说,基数排序的实质是多关键字排序。
多关键字排序时有两种解决方案:
最高位优先法(MSD)(Most Significant Digit first)
最低位优先法(LSD)(Least Significant Digit first)
这里我们采用LSD,对关键字的排序采用桶排序,那么实质上就变成了多次桶排序.
时间复杂度为O(d(n+k),d为关键字个数,k为取值范围
数字是四位数之内,所以四个关键字为个十百千位的值.
//假定四位数字内比较,有四个关键字,个十百千位,同一关键字使用桶排序 public static void radixSort(int[] a,int max,int d){ int rate=1;//表示关键字层级 int[] buckets=new int[max];//存放个位数,十位数,百位数.... List<Integer>[] temp=new List[max];//存放缓存数字 for(int i=0;i<d;i++){
//清空操作 Arrays.fill(buckets, 0); Arrays.fill(temp, null);
//计算每个待排序数据的子关键字 for(int j=0;j<a.length;j++){ int subKey=(a[j]/rate)%max;//取得个位数,十位数,百位数... if(temp[subKey]==null) temp[subKey]=new ArrayList<Integer>();//用list数组来存放缓存数字 temp[subKey].add(a[j]); buckets[subKey]++;//计数+1 }
//进行排序,就是按顺序取 for(int j=0,k=0;j<max;j++){ int t=0; while((buckets[j]--)>0){ a[k++]=temp[j].get(t); t++; } }
rate*=max; } }
由于数组操作起来是比较麻烦,非要通过数组存储的话只能存储次数不能存储整个数字,那取出来的时候我还没想到好的方法把它还原成数字了.所以我就采取了很笨的方法,再用一个缓存list数组存数字...
当然这很浪费空间,也很麻烦,直接用链表简单很多
public static void linkedRadixSort(int[] a,int max,int d){ ArrayList<ArrayList> list=new ArrayList<ArrayList>(); int rate=1;
for(int i=0;i<d;i++){ list.clear(); for(int j=0;j<max;j++){ list.add(new ArrayList<Integer>()); } for(int j=0;j<a.length;j++){ int num=a[j]; int subKey=(num/rate)%max; list.get(subKey).add(num); } for(int j=0,k=0;j<max;j++){ while(list.get(j).size()>0 && list.get(j)!=null){ a[k]=(Integer) list.get(j).remove(0); k++; } }
rate*=max; } }
虽然说是用链表的,但是收集起来的时候仍然是放数组的,仍要开辟许多空间.于是我们采用链式基数排序
所谓链式,就是用链表存储,前一个分组的尾指针指向下一个分组的头指针这样,这样链表很容易做到.
public static void linkedRadixSortS(Integer[] a,int max,int d){ List<Integer> slist=new ArrayList<Integer>(); ArrayList<ArrayList> list=new ArrayList<ArrayList>(); int rate=1; slist.addAll(Arrays.asList(a));
for(int i=0;i<d;i++){ list.clear(); for(int j=0;j<max;j++){ list.add(new ArrayList<Integer>()); } while(slist.size()>0){ int num=slist.remove(0); int subKey=(num/rate)%max; list.get(subKey).add(num); } for(int j=0;j<max;j++){ slist.addAll(list.get(j)); } rate*=max; }
a=slist.toArray(a); }
上面三种只是空间上的优化,对于时间复杂度是没有影响的.
外部排序
之前所有的排序算法都属于内部排序,需要将输入数据装入内存中.然而在一些应用程序中,它们的输入数据量太大装不进内存,这时候就需要外部排序了.
基本外部排序算法使用归并排序.设有四盘磁带,Ta1,Ta2,Tb1,Tb2,磁带a和磁带b或者用作输入磁带,或者用于输出磁带.从输入磁带一次读入M(我们取3)个记录,在内部将这些记录排序,然后再交替地写到Tb1或Tb2上,将每组排过序的记录叫做一个顺串.
现在Tb1和Tb2都包含了一些顺串.我们将每个磁带的第一个顺串取出将两者合并,把结果写到Ta1上,该结果是一个二倍长的顺串.然后我们再从每盘磁带取出下一个顺串,合并然后写到Ta2上.继续这个过程直达Tb1或Tb2为空.继续这个过程直到得到长为N的一个顺串,这个算法需要log(N/M)趟工作,见下图
如果我们有额外的磁带,那么我们可以减少将输入数据排序的趟数,将基本的(2-路)合并扩充为(k-路)合并就可以做到这一点.这就是所谓的多路合并
上面讨论的k-路合并方案需要使用2k盘磁带,其实我们通过只使用k+1盘磁带也可以完成排序的工作,就是所谓的多相合并.以三盘磁带完成2-路合并为例:
这儿顺串最初的分配是一个关键的问题,其实采用斐波那契数是最优的.
对于顺串的构造还可以采用置换选择排序,采用堆的思想构建,具有特别的价值.
接下来实现完整的外部排序
package com.fredal.structure;
import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.util.ArrayList;import java.util.Arrays;import java.util.Iterator;import java.util.Random;
public class ExternalSort {
public static int BUFFER_SIZE = 10;
public File sort(File file) throws IOException { ArrayList<File> files = split(file); return process(files); }
// recursive method to merge the lists until we are left with a // single merged list private File process(ArrayList<File> list) throws IOException { if (list.size() == 1) { return list.get(0); } ArrayList<File> inter = new ArrayList<File>(); for (Iterator<File> itr = list.iterator(); itr.hasNext();) { File one = itr.next(); if (itr.hasNext()) { File two = itr.next(); inter.add(merge(one, two)); } else { return one; } } return process(inter); }
/** * Splits the original file into a number of sub files. */ private ArrayList<File> split(File file) throws IOException { ArrayList<File> files = new ArrayList<File>(); int[] buffer = new int[BUFFER_SIZE]; FileInputStream fr = new FileInputStream(file); boolean fileComplete = false; while (!fileComplete) { int index = buffer.length; for (int i = 0; i < buffer.length && !fileComplete; i++) { buffer[i] = readInt(fr); if (buffer[i] == -1) { fileComplete = true; index = i; } } if (buffer[0] > -1) { Arrays.sort(buffer, 0, index); File f = new File("set" + new Random().nextInt()); FileOutputStream writer = new FileOutputStream(f); for (int j = 0; j < index; j++) { writeInt(buffer[j], writer); } writer.close(); files.add(f); }
} fr.close(); return files; }
/** * Merges two sorted files into a single file. * * @param one * @param two * @return * @throws IOException */ private File merge(File one, File two) throws IOException { FileInputStream fis1 = new FileInputStream(one); FileInputStream fis2 = new FileInputStream(two); File output = new File("merged" + new Random().nextInt()); FileOutputStream os = new FileOutputStream(output); int a = readInt(fis1); int b = readInt(fis2); boolean finished = false; while (!finished) { if (a != -1 && b != -1) { if (a < b) { writeInt(a, os); a = readInt(fis1); } else { writeInt(b, os); b = readInt(fis2); } } else { finished = true; }
if (a == -1 && b != -1) { writeInt(b, os); b = readInt(fis2); } else if (b == -1 && a != -1) { writeInt(a, os); a = readInt(fis1); } } os.close(); return output; }
private void writeInt(int value, FileOutputStream merged) throws IOException { merged.write(value); merged.write(value >> 8); merged.write(value >> 16); merged.write(value >> 24); merged.flush(); }
private int readInt(FileInputStream fis) throws IOException { int buffer = fis.read(); if (buffer == -1) { return -1; } buffer |= (fis.read() << 8); buffer |= (fis.read() << 16); buffer |= (fis.read() << 24); return buffer; }
/** * @param args * @throws IOException */ public static void main(String[] args) throws IOException { File file = new File("mainset"); Random random = new Random(System.currentTimeMillis()); FileOutputStream fw = new FileOutputStream(file); for (int i = 0; i < BUFFER_SIZE * 3; i++) { int ger = random.nextInt(); ger = ger < 0 ? -ger : ger; fw.write(ger); fw.write(ger >> 8); fw.write(ger >> 16); fw.write(ger >> 24); } fw.close(); ExternalSort sort = new ExternalSort(); System.out.println("Original:"); dumpFile(sort, file); File f = sort.sort(file); System.out.println("Sorted:"); dumpFile(sort, f);
}
private static void dumpFile(ExternalSort sort, File f) throws FileNotFoundException, IOException { FileInputStream fis = new FileInputStream(f); int i = sort.readInt(fis); while (i != -1) { System.out.println(Integer.toString(i)); i = sort.readInt(fis); } }
}
数据结构_排序算法总结相关推荐
- 希尔排序python 简书_数据结构_排序_直接插入+希尔排序
数据结构_排序_直接插入排序+希尔排序 其实主要是为了讲述希尔排序,不过插入排序是希尔排序的基础,因此先来讲直接插入排序. 一.直接插入排序 1.原理 下标 0 1 2 3 4 5 6 7 8 -- ...
- 常用数据结构以及数据结构的排序算法
2019独角兽企业重金招聘Python工程师标准>>> 数组 (Array) 在程序设计中,为了处理方便, 把具有相同类型的若干 变量按有序的形式组织起来.这些按序排列的同类数据元素 ...
- 【数据结构】——排序算法——2.1、冒泡排序
[数据结构]--排序算法--2.1.冒泡排序 一.先上维基的图: 图一.冒泡排序 分类 排序 ...
- JAVA堆排序有监视哨吗_数据结构-各类排序算法总结
各类排序算法总结 一. 排序的基本概念 排序(Sorting)是计算机程序设计中的一种重要操作,其功能是对一个数据元素集合或序列重新排列成一个按数据元素某个项值有序的序列. 有 n 个记录的序列{R1 ...
- 【恋上数据结构】排序算法前置知识及代码环境准备
排序准备工作 何为排序? 何为稳定性? 何为原地算法? 时间复杂度的知识 写排序算法前的准备 项目结构 Sort.java Asserts.java Integers.java Times.java ...
- python语言基本排序算法_排序算法(Python)
参考: <数据结构(Python 语言描述)> - 3.4 基本排序算法.3.5 更快的排序 Tips:为了保持简洁,每个函数都只处理整数列表,并且假设列表不为空. 目录.jpg 术语 1 ...
- java排序算法总结_排序算法总结及Java实现
1. 整体介绍 分类 排序大的分类可以分为两种,内排序和外排序.在排序过程中,全部记录存放在内存,则称为内排序,如果排序过程中需要使用外存,则称为外排序.主要需要理解的都是内排序算法: 内排序可以分为 ...
- 【数据结构】排序算法及优化整理
排序算法 排序算法 选择排序 Selection Sort 插入排序 Insertion Sort 归并算法 Merge Sort 快速排序 Quick Sort 堆排序 Heap Sort 二叉堆的 ...
- python实现希尔排序算法_排序算法总结(冒泡排序、直接插入排序、希尔排序)(python实现)...
其实本文叫排序算法总结有点过了,只是用python实现了一遍.本文都是参照一篇csdn博客<数据结构排序算法>,里面详细介绍每种排序算法的原理,并给出了C++的实现,再次膜拜. # -*- ...
最新文章
- 递归遍历Linux下的目录文件源码实现
- 敏捷软件开发--计划
- Windows的启动u盘linux,如何在linux下制作一个windows的可启动u盘?
- LiveVideoStackCon 2021北京站 9月再次启航!
- 如何安装最新版本的 SAP ABAP Development Tool ( ADT ) 2021年度更新
- 外国经典儿童读物合集pdf_帮助父母在线购买儿童读物–用户体验案例研究
- 基于Ocelot的gRpcHttp网关
- 面试题,你觉得XX和XX产品有何区别?
- html中dom和bom,区分BOM和DOM,区分window、document、html、body
- onvif学习笔记5:onvif框架代码初步了解
- maven打包忽略注解_Maven打包时遇到的一些坑和解决方案
- POI 操作Excel添加超链接
- Python学习笔记10:内建结构
- 邮件服务器1---原理以及基本概念
- 从怎样解题到怎样解决问题
- Ubuntu桌面美化教程
- 360P 480P 720P 1080P 1080i 说明
- 抗超大规模DDOS攻击
- 等待输入超时:自动登出
- python数据记录_python 数据处理中的记录
热门文章
- 2018年中国视频监控行业现状及行业发展趋势分析预测【图】
- 获得变量的名称获得传入参数的参数类型与堆栈中的函数名获得变量的名称
- python调用 matlab库_python调用matlab的搜索结果-阿里云开发者社区
- mysql自增_面试官:为什么 MySQL 的自增主键不单调也不连续?
- native react 常用指令_React Native入门基础篇(一)
- python数据动画_[转载]Maya使用Python获取动画每帧的rotation数据
- android gps 锁屏更新坐标_把手机锁屏设置成任意字体,悄悄给男(女)朋友一个惊喜吧...
- 转:A/B测试:实现方法
- 求凸包(两遍扫描,求上下凸包的方法)
- Oracle 跨库 查询 复制表数据 分布式查询