排序算法自我总结

最近在复习,总会遇到一些关于排序算法的问题,所以最近抽了点时间,对常用的几种排序算法进行了一点总结;

其中包括最简单的冒泡排序,直接选择排序,直接插入排序,插入排序的进化版:希尔排序,堆排序,最常用的快速排序,还有递归,二分的典型运用:归并排序(并轨排序,合并排序)。

排序算法,每个人都有每个人的实现方法,但是大体思想是一致的,在此就将自己的想法分享给大家。接下来,我将从思想,时间复杂度,稳定性以及实现代码等方面一一介绍。

一、冒泡排序

    作为最简单的排序算法,我相信每个程序员初学者都应该是第一个接触的排序算法,不仅仅是因为想法简单,更重要的是代码量少,代码实现方便;

思想:两两比较,最大的移到最后一个,以此类推

代码:

void bubble_sort(int *arr, int len)

{
if (arr == NULL || len <= 0)
{
return;
}
int i, j;
for (i = 0; i < len-1; ++i)
{
for (j = i+1; j < len; ++j)
{
if (arr[j] <arr[i])
{
swap(arr[i], arr[j]);
}
}
}

}

时间复杂度:由上面的代码我们可以看出,若文件的初始状态是有序的,我们只需要一趟扫描,时间复杂度为O(n),最差的情况下,也就是反序的,则需要比较n*n次,时间复杂度为O(n^2),(平均)时间复杂度为O(n^2)。

稳定性:冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无聊地把他们俩交换一下的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。


二 、直接选择排序

思想:遍历数组,遍历到i时,a0,a1...ai-1是已经排好序的,然后从i到n选择出最小的,记录下位置,如果不是第i个,则和第i个            元素交换。此时第i个元素可能会排到相等元素之后,造成排序的不稳定。

代码:

void select_sort(int *arr, int len)
{
if (arr == NULL || len <= 0)
{
return;
}
int i, j, min_index;
for (i = 0; i < len; ++i)
{
min_index = i;
for (j = i + 1; j < len; ++j)
{
if (arr[j] < arr[min_index])
{
min_index = j;
}
}
if (min_index != i)
{
swap(arr[i], arr[min_index]);
}
}
}

时间复杂度:(平均)时间复杂度O(n^2),最坏时间复杂度O(n^2),最好情况的时间复杂度是O(n^2)

稳定性:选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果一个元素比当前元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。比较拗口,举个例子,序列5 8 5 2 9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中两个5的相对前后顺序就被破坏了,所以选择排序是一个不稳定的排序算法。

三、直接插入算法

思想:最通俗的解释就是,从头开始拿元素,记录当前元素,如果比前面的小,前面的就后移(直接赋值,最开始记录了元素,不会丢失)。由于

            是从头开始一个一个排序,所以前面一定是有序的,直到当前元素比前面大,那么当前元素就应该插入在前面元素的后面位置。

代码:

void insert_sort(int *arr,int len)
{
if (arr == NULL || len <= 0)
{
return;
}
int i, pre,temp;
for (i = 1; i < len; ++i)
{
temp = arr[i];
pre = i - 1;
while (pre>=0&&arr[pre]>temp)
{
arr[pre + 1] = arr[pre];
pre--;
}
arr[pre + 1] = temp;
}
}

时间复杂度:(平均)时间复杂度O(n^2),最坏时间复杂度为O(n^2),最好时间复杂度是O(n)。

稳定性:插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。

四、快速排序

思想:快速排序,最常用的一种排序方式快速排序首先找到一个基准,下面程序以第一个元素作为基准(temp),然后先从右向左搜索,如果发现比temp小,则和temp交换,然后从左向右搜索,如果发现比temp大,则和temp交换,一直到左边大于右边,此时temp左边的都比它小,而右边的都比它大,此时temp的位置就是排好序后应该在的位置,此时temp将数组划分为左右两部分,可以递归采用该方法进行。

代码:

int mpartition(int *arr, int left, int right)

{
int temp = arr[left];
while (left < right)
{
while (left < right&&arr[right] >= temp)
{
right--;
}
if (left < right)
{
arr[left++] = arr[right];
}
while (left < right&&arr[left] < temp)
{
left++;
}
if (left < right)
{
arr[right--] = arr[left];
}
}
arr[left] = temp;
return left;
}
void  quick_sort(int *arr,int left, int right)
{
if (arr == NULL)
{
return;
}
if (left < right)
{
int cur = mpartition(arr, left, right);
quick_sort(arr, left, cur-1);
quick_sort(arr, cur + 1, right);
}

}

时间复杂度:

(平均)时间复杂度O(nlogn),最好时间复杂度O(nlogn),最坏时间复杂度O(n^2)

稳定性:快排的交换使排序成为不稳定的

五、堆排序

思想:先把数组当做是一个完全二叉树,每一个结点的孩子是n*2+1和n*2+2,我们只要从最后一个非叶子节点开始调整二叉树(堆)的结构,可以使大顶堆,也可以是小顶堆,依次往上调整,直到整棵树都是一个大顶堆(小顶堆);取堆顶元素与最后一个元素交换,也就是数组的最后一个;然后把除了最后一个元素的数组再次调整为大顶堆(小顶堆),以此类推

代码:

void heap_adjust(int *arr,int pos,int len)
{
int index_left = pos * 2 + 1;
int index_right = pos * 2 + 2;
int maxindex = pos;
if (index_left < len&&arr[index_left] >arr[maxindex])
{
maxindex = index_left;
}
if (index_right < len&&arr[index_right] > arr[maxindex])
{
maxindex = index_right;
}
if (maxindex != pos)
{
swap(arr[maxindex], arr[pos]);
heap_adjust(arr, maxindex, len);
}


}
void heap_sort(int *arr, int len)
{
if (arr == NULL || len <=0)
{
return;
}
for (int pos = len / 2 - 1; pos >= 0; --pos)//pos为第一个非叶子节点
{
heap_adjust(arr, pos, len);
}
for (int i = len - 1; i >=1; --i)
{
// 把第一个元素和当前的最后一个元素交换,
// 保证当前的最后一个位置的元素都是在现在的这个序列之中最大的
swap(arr[0], arr[i]);
// 不断缩小调整heap的范围,每一次调整完毕保证第一个元素是当前序列的最大值
heap_adjust(arr, 0, i);
}
}

时间复杂度:

(平均)时间复杂度O(nlogn);最坏时间复杂度O(n^2),最好情况的时间复杂度是O(nlogn)

稳定性:不稳定

六、归并排序

思想:简单来说,分分合合,意思就是先把所有的数分开成一部分,然后把最小的变成有序的,然后合到一起。  归并排序是采用分治法(Divide and Conquer)的一个非常典型的应用。首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。这需要将待排序序列中的所有记录扫描一遍,因此耗费O(n)时间,而由完全二叉树的深度可知,整个归并排序需要进行.logn.次,因此,总的时间复杂度为O(nlogn)。归并排序在归并过程中需 要与原始记录序列同样数量的存储空间存放归并结果,因此空间复杂度为O(n)。

代码:

void Merge1(int *arr, int left,int mid, int right)
{
int len1 = mid - left + 1;
int len2 = right - mid;
int *temp1 = new int[len1];
int *temp2 = new int[len2];
for (int i = 0; i < len1; ++i)
{
temp1[i] = arr[left+i];
}
for (int i = 0; i < len2; ++i)
{
temp2[i] = arr[mid+i+1];
}
int m = 0,n = 0;
for (int i = left; i <= right; i++)
{
if (m<len1 && n<len2)
{
arr[i] = temp1[m]<temp2[n] ? temp1[m++] : temp2[n++];
}
else
{
if (m<len1)
{
arr[i] = temp1[m++];
}
else
{
arr[i] = temp2[n++];
}
}
}
delete[]temp1;
delete[]temp2;


}
void Merge_sort(int *arr, int left, int right)
{
if (left < right)
{
int mid = (left+right) / 2;
Merge_sort(arr, left, mid);//拆分左边,直到只剩一个
Merge_sort(arr, mid + 1, right);//拆分右边,直到只剩一个
Merge1(arr, left, mid,right);//把拆分的合并到一起
}

}

时间复杂度:

(平均)时间复杂度O(nlogn);最坏时间复杂度O(nlogn),最好情况的时间复杂度是O(n)

稳定性:稳定

七、希尔排序

话不多说,直接看代码

void shell_sort(int *arr, int len)
{
int decrease = len / 2;   //decrease记录增量
int temp;
for (; decrease >= 1; decrease = decrease / 2)//增量每次取半,直到增量为1
{
for (int i = decrease; i < len; ++i)     //从第一个增量开始,往后的都得比较
{
temp = arr[i];                       //记录当前被比较的值
int pos = i - decrease;              //找到当前位置减去增量的位置
while (pos >= 0 && arr[pos] > temp)  //插入条件
{
arr[pos + decrease] = arr[pos];  //符合条件,(前面的大于后面的),前面的值赋给后面
pos = pos - decrease;            //pos再往前推一个增量
}
arr[pos + decrease] = temp;          //前面的赋给后面的了,所以最终最前面的需要用temp赋值
}
}

}

时间复杂度:

(平均)时间复杂度O(n(logn)^2)。最坏时间复杂度O(n(logn)^2),最好情况的时间复杂度是O(n)

稳定性:不稳定

其实还有一个排序叫做基数排序,我打算在和BST树(二叉排序树)一起说明。全部手打,可能有错,望读者指出,必然改正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值