一、冒泡排序:
- 从小到大排序举例。
两个相邻元素之间,两两进行相互比较,若前一个数比后一个数大,则交换位置。每经过一次循环,则都把较大的一个数放在后面。
public void bubbeSort(int [] arr){
if(arr == null||arr.length < 2){
return ;
}
for(int i = 0 ; i <arr.length-1 ; i ++){
for(int j = 1; j < arr.length-i ; j ++){
if(arr[j-1] > arr[j]){
swap(arr,j-1,j);
}
}
}
}
void swap(int[] arr, int i, int j) {
int temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
- 可以设计一个标识符,如果一次循环发生交换,则为true,否则为false,如果一次没有发生交换,说明排序已经完成。
public void bubbeSort(int [] arr){
boolean flag = true;
if(arr == null||arr.length < 2){
return ;
}
while(flag){
flag = false;
for(int i = 0 ; i <arr.length-1 ; i ++){
if(arr[i] > arr[i + 1]){
swap(arr,i,i+1);
flag = true;
}
}
}
}
- 冒泡排序时间复杂度为:O(n²)
二.插入排序(直接插入排序)
- 直接插入排序:
直接插入排序就是从待排序列中选出一个元素,插入到已经有序的元素之中,直到所有的元素都插入到有序序列中所有的元素就全部
有序了。
通常的做法就是将第一个元素看做是有序的元素(即待排序列的第一个元素看做是有序序列),然后我们将第二个元素和有序序列(即
第一个元素)作比较,按正确的序列插入到序列中去。然后在将第三个元素和前面有序序列(即整个待排序列的前两个元素)作比较,将第
三个插入到前两个元素中去,使得前三个元素有序。以此类推,直到所有的元素都有序。
void insertSort(int [] arr){
if(arr == null||arr.length < 2){
return ;
}
for(int i = 1; i < arr.length; i++){ //因为我们要对该待排序列的每一个元素都和前面的已排好序的序列进行插入,所以我们会对序列进行遍历
for(int j = 0; j < i; j++){ //第二层循环主要用于对已排好序的序列进行扫描,和要插入进来的数据进行逐一比较,然后决定插入到哪里
if(arr[j] > arr[i]){ //从前往后对已排好序的元素和待插入元素进行大小比较,然后直到找到一个元素比被插入元素大,则交换位置
swap(arr,i,j);
}
}
}
}
- 最坏情况下的复杂度为 O(n²)
最好情况下插入排序的时间复杂度为O(N)。
三,希尔排序
- 希尔排序的基本思想就是:将需要排序的序列划分为若干个较小的序列,对这些序列进行直接插入排序,通过这样的操作可使需要排序的数列基本有序,最后再使用一次直接插入排序。
void shellSort(int [] arr){
int gap;
int len=arr.length;
for(gap = len/2;gap>0;gap=gap/2){
for(int i = gap ;i < len; i++) {
for(int j = i - gap;j>=0&&arr[j]>arr[j+gap]; j-=gap) {
swap(arr,j,j+gap);
}
}
}
}
- 小规模数据或基本有序数据使用十分有效
希尔排序的运行时间依赖于增量序列的选择。
使用希尔增量时希尔排序的最坏情形运行时间为θ(N2)。
Hibbard增量序列:1,4,7,…,2k-1。这个增量的特点是增量没有公因子
四.直接选择排序
- 直接选择排序和直接插入排序类似,都将数据分为有序区和无序区,所不同的是直接插入排序是将无序区的第一个元素直接插入到有序区以形成一个更大的有序区,而直接选择排序是从无序区选一个最小的元素直接放到有序区的开头
void selectSort(int [] arr){
int nMinIndex;
int n=arr.length;
for (int i = 0; i < n; i++)
{
nMinIndex = i; //找最小元素的位置
for (int j = i + 1; j < n; j++)
if (arr[j] < arr[nMinIndex])
nMinIndex = j;
swap(arr,i, nMinIndex); //将这个元素放到无序区的开头
}
}
五.归并排序
- 归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可
void mergesort(int a[], int first, int last, int temp[])
{
if (first < last)
{
int mid = (first + last) / 2;
mergesort(a, first, mid, temp); //左边有序
mergesort(a, mid + 1, last, temp); //右边有序
mergearray(a, first, mid, last, temp); //再将二个有序数列合并
}
}
void mergearray(int a[], int first, int mid, int last, int temp[]){
int i = first, j = mid + 1;
int m = mid, n = last;
int k = 0;
while (i <= m && j <= n){
if (a[i] < a[j]) {
temp[k++] = a[i++];
}
else {
temp[k++] = a[j++];
}
}
while (i <= m) {
temp[k++] = a[i++];
}
while (j <= n) {
temp[k++] = a[j++];
}
for (i = 0; i < k; i++) {
a[first + i] = temp[i];
}
}
- 时间复杂度为 O(nlogn)
六.快速排序
*快速排序是一种分治的排序算法。它将一个数组分成两个子数组,将两部分独立地排序。快速排序和归并排序是互补的:归并排序将数组分成两个子数组分别排序,并将有序的子数组归并以将整个数组排序;而快速排序将数组排序的方式则是当两个子数组都有序时整个数组也就自然有序了。在第一种情况中,递归调用发生在处理整个数组之前;在第二种情况中,递归调用发生在处理整个数组之后。在归并排序中,一个数组被等分为两半;在快速排序中,切分的位置取决于数组的内容
void quickSort1(int s[], int l, int r){
if (l < r){
int i = partition(s, l, r);//先成挖坑填数法调整s[]
quick_sort1(s, l, i - 1); // 递归调用
quick_sort1(s, i + 1, r);
}
}
int partition(int s[], int l, int r) { //返回调整后基准数的位置
int i = l, j = r;
int x = s[l]; //s[l]即s[i]就是第一个坑
while (i < j){
// 从右向左找小于x的数来填s[i]
while(i < j && s[j] >= x) {
j--;
}
if(i < j){
s[i] = s[j]; //将s[j]填到s[i]中,s[j]就形成了一个新的坑
i++;
}
// 从左向右找大于或等于x的数来填s[j]
while(i < j && s[i] < x) {
i++;
}
if(i < j) {
s[j] = s[i]; //将s[i]填到s[j]中,s[i]就形成了一个新的坑
j--;
}
}
//退出时,i等于j。将x填到这个坑中。
s[i] = x;
return i;
}
- 时间复杂度为 O(nlogn)
是否稳定
- 稳定的排序:
冒泡排序
插入排序
归并排序
基数排序
- 不稳定的排序
选择排序
快排排序
希尔排序
堆排序