一 冒泡排序
重复地走访过要排序的元素列,依次比较两个相邻的元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来
2、步骤:
-
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
-
对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
-
针对所有的元素重复以上的步骤,除了最后一个。
-
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较
//升序
public static void order1(int[] a) {
boolean hasSwap;
for(int x=0;x<a.length-1;x++) {
hasSwap = false;
for (int y = 0; y < a.length-1-x; y++) {
if(a[y]>a[y+1]) {
int t = a[y];
a[y] = a[y+1];
a[y+1] = t;
hasSwap = true;
}
}
if(hasSwap == false) return;
}
」
冒泡排序优化
以递增序列为例
双指针法,双循环中,每次循环最大数到末尾,最小数到开头,此时 收尾已经是有序的了,一次,可以将比较的指针 left和right分别向前移动一位
a,b,c,d,e,f,g
第一次:a 和g有序
第二次:b和f有序
…
/**
*进行双向的循环,正向循环把最大元素移动到末尾,逆向循环把最小元素移动到最前
*/
private void bubbleSort3() {
int[] is ={95,85,12,52,64,74,105,502,4,7,6,1,74,60,141,19,34,45,59};
Log.i(TAG, "开始--"+ Arrays.toString(is));
int handleCount=0;
int swapCount=0;
int endPoint=is.length-1;//右指针
int startPoint=0;//左指针
while (startPoint<=endPoint){
int newEndPoint=startPoint;
int newStartPoint=endPoint;
for (int j=startPoint;j<endPoint;j++){
if (is[j]>is[j+1]){
swap(is,j,j+1);
swapCount++;
newEndPoint=j+1;
}
handleCount++;
}
endPoint=newEndPoint-1;//这里一个元素已经沉底了,所以下一次交换次数相比于最后一次交换要少1
for (int j=endPoint;j>startPoint;j--){
if (is[j]<is[j-1]){//后一个元素小于上一个元素,往上移动一次
swap(is,j,j-1);
swapCount++;
newStartPoint=j-1;
}
handleCount++;
}
startPoint=newStartPoint+1;//这里一个元素到最上面了,所以下一次交换次数相比于上一次交换要少1
}
Log.i(TAG, "bubbleSort3: --handleCount:"+handleCount+",swapCount:"+swapCount+"\r\n"+ Arrays.toString(is));
}
二 选择排序
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到全部待排序的数据元素排完。 选择排序是不稳定的排序方法
2、步骤:
- 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始 位置。
- 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
- 重复第二步,直到所有元素均排序完毕。
//升序
public static void order2(int[] a) {
for (int i = 0; i < a.length-1; i++) {
for (int j = i + 1; j < a.length; j++) {
if (a[i] > a[j]) {
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}
}
}
插入排序
插入算法把要排序的数组分成两部分:第一部分包含了这个数组的所有元素,但将最后一个元素除外(让数组多一个空间才有插入的位置),而第二部分就只包含这一个元素(即待插入元素)。在第一部分排序完成后,再将这个最后元素插入到已排好序的第一部分中。
2、步骤:
- 从第一个元素开始,该元素可以认为已经被排序
- 取出下一个元素,在已经排序的元素序列中从后向前扫描
- 如果该元素(已排序)大于新元素,将该元素移到下一位置
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
- 将新元素插入到下一位置中
- 重复步骤2~5
//从哪边取数,必须从对边开始扫描
public static void order3(int[] a) {
for (int i = 1; i < a.length; i++) {//外循环表示比较次数
int get = a[i];//去除i位置的数
int j = i-1;
while (j >= 0 && a[j] > get) {
a[j + 1] = a[j];
j--;
}
a[j + 1] = get;
}
}
归并排序
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
2、步骤:
- 把长度为n的输入序列分成两个长度为n/2的子序列。
- 对这两个子序列分别采用归并排序。
- 将两个排序好的子序列合并成一个最终的排序序列。
/**
* 归并排序
*/
public static void order4(int[] a) {
sort(a,0,a.length-1);
}
public static void sort(int[] data,int left,int right) {
if(left >= right) return;
//找出中间索引
int center = (left + right)/2;
//对左边数组进行递归
sort(data, left, center);
//对右边数组进行递归
sort(data, center+1, right);
//合并
merge(data,left,center,right);
}
public static void merge(int[] data,int left, int center,int right) {
//临时数组
int[] tmpArr = new int[data.length];
//右数组第一个元素索引
int mid = center + 1;
//third 记录临时数组的索引
int third = left;
//缓存左数组第一个元素的索引
int tmp = left;
while (left <= center && mid <=right) {
//从两个数组中取出最小的放入临时数组
if (data[left] <= data[mid]) {
tmpArr[third++] = data[left++];
} else {
tmpArr[third++] = data[mid++];
}
}
//剩余部分依次放入临时数组(实际上两个while只会执行其中一个)
while(mid <= right) {
tmpArr[third++] = data[mid++];
}
while(left <= center) {
tmpArr[third++] = data[left++];
}
//将临时数组中的内容拷贝回原数组中
while (tmp <= right) {
data[tmp] = tmpArr[tmp++];
}
}
快速排序
1、简介:
在数组中随机选一个数(默认数组首个元素),数组中小于等于此数的放在左边,大于此数的放在右边,再对数组两边递归调用快速排序,重复这个过程。
2、步骤:
- 先从数列中取出一个数作为key值;
- 将比这个数小的数全部放在它的左边,大于或等于它的数全部放在它的右边;
- 对左右两个小数列重复第二步,直至各区间只有1个数。
/**
* 快速排序
*/
public static void order5(int[] a) {
quickSort(a,0,a.length-1);
}
private static void quickSort(int[] a, int left, int right) {
if(left < right) {
int i = getMiddle(a,left,right);
quickSort(a, left, i - 1);
quickSort(a, i + 1,right);
}
}
private static int getMiddle(int[] a, int low, int high) {
int pivot = a[low];
int i = low;
int j = high;
while(i < j) {
while(pivot <= a[j] && i < j) j--;
while(pivot >= a[i] && i < j) i++;
if(i < j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
a[low] = a[i];
a[i] = pivot;
return i;
}
希尔排序
1、简介:
希尔排序是插入排序改良的算法,希尔排序步长从大到小调整,第一次循环后面元素逐个和前面元素按间隔步长进行比较并交换,直至步长为1,步长选择是关键。
2、先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
- 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
- 按增量序列个数k,对序列进行k 趟排序;
- 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
/**
* 希尔排序
*/
public static void order6(int[] a) {
int len = a.length;//单独把数组长度拿出来,提高效率。
while(len != 0) {
len = len/2;
for (int i = 0; i < len; i++) {//分组
for (int j = i + 1; j < a.length; j+=len) {//元素从第二个开始
int k = j - len;//k为有序序列最后一位的位数
int temp = a[j];//要插入的元素
while (k >= 0 && temp < a[k]) {//从后往前遍历
a[k + len] = a[k];
k -= len;//向后移动len位
}
a[k + len] = temp;
}
}
}
}
场景应用:
(1)若n较小(如n≤50),可采用直接插入或直接选择排序。
当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。
(2)若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜;
(3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。
快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。
若要求排序稳定,则可选用归并排序。但本章介绍的从单个记录起进行两两归并的 排序算法并不值得提倡,通常可以将它和直接插入排序结合在一起使用。先利用直接插入排序求得较长的有序子文件,然后再两两归并之。因为直接插入排序是稳定 的,所以改进后的归并排序仍是稳定的。