冒泡排序
冒泡排序的思想:冒泡排序是一种简单的排序算法,它重复的走访过要排序的序列,一次比较两个相邻两个元素。如果他们的顺序错误就讲他们的顺序交换过来,这个算法的名字是因为越小的元素会慢慢的“冒”到序列的最前端,就像水中冒出的气泡一样。
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定(排序的稳定性是指如果在排序的序列中,存在前后相同的两个元素的话,排序前 和排序后他们的相对位置不发生变化))
冒泡排序示例:
示例代码如下:
/*冒泡排序
*比较两个相邻的元素,如果第一个元素大于第二个,那么就把两个元素交换。
*对每一对相邻的元素都做同样的工作,从开始第一个到对尾最后一个。
*对数列的每一次走访后,数列的最后一个数都是走访得到的最大数。这样下一次走访就不要在与这次得到的最大的数比较了。
*持续对以上越来越少的元素的比较工作,直至最后一个。
*/
private void sort(int[] number)
{
int tempMax;
int length=number.length;
for(int i = 0;i < length-1;i++)
{
for(int j=0;j<length-1-i;j++)
{
if(number[i]>number[i+1])
{
tempMax=number[i];
number[i]=number[i+1];
number[i+1]=tempMax;
}
}
}
}
快速排序
快速排序的基本思想:通过将数列分割成两个部分,其中一部分的元素都比另一部分的元素小,在分别两部分继续进行同样的排序,直到整个数列的有序。
时间复杂度:O(nlogn)
空间复杂度:O(1)
快速排序算法是不稳定的。
//low是默认中轴,high是数列最右边的位置
//左边的都比这个位置小,右边的都大
public static int getMiddle(int[] numbers, int low,int high)
{
int temp = numbers[low]; //数组的第一个作为中轴
while(low < high)
{
while(low < high && numbers[high] >= temp)
{
high--;
}
numbers[low] = numbers[high];//比中轴小的记录移到低端
while(low < high && numbers[low] <= temp)
{
low++;
}
numbers[high] = numbers[low] ; //比中轴大的记录移到高端
}
numbers[low] = temp ;
return low ; // 返回中轴的位置
}
递归分算两部分的排序:
public static void quickSort(int[] number)
{
int low = 0;
int high = number.length-1;
if(low < high)
{
int middle = getMiddle(numbers,low,high); //将numbers数组进行一分为二
quickSort(numbers, low, middle-1); //对低字段表进行递归排序
quickSort(numbers, middle+1, high); //对高字段表进行递归排序
}
}
选择排序
选择排序的的基本思想:在数列中选出最小的元素与第一个位置的元素交换,然后在剩下的数中找到最小的元素与第二个位置的元素交换位置,直到倒数第二个和最后一个元素进行比较。
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:不稳定
代码实现:
private void SelectSort(int[] number)
{
for(int i=0;i<number.length;i++)
{
for(int j=number.length-1;j>i:j--)
{
if(number[j]<number[i])
{
int temp = number[j];
number[j] = number[i];
number[i] = temp;
}
}
}
}
总结:每一趟排序将会选择出最小的(或者最大的)值,顺序放在已排好序的数列的前面(或后面)。
插入排序
思想:默认将一个数列的第一个元素看做一个有序表,将后面的元素按照大小一次插入到合适的位置。
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
代码实现
private void InsertSort(int[] number)
{
for(int i=1;i<number.length;i++)
{
int temp = number[i];
for(int j=i;j>0&&temp<number[j-1];j--)
{
number[j]=number[j-1];
}
number[j]=temp;
}
}
折半插入排序
折半插入排序是基于插入排序写的,可以减少“移动”和“比较”的次数。
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
private void BInsertSort(int[] number)
{
for (int i = 1; i < number.Length; i++) {
int temp = number [i];
int j = 0;
int low = 0, high = i - 1;
while (low<=high) {
int m = (low + high) / 2; //找到折半点
if (temp < number [m]) {
high = m - 1;
} else {
low = m + 1;
}
}
for (j = i; j >high+1; j--) {
number [j] = number [j - 1];
}
number [j] = temp;
}
}
希尔排序
思想:希尔排序也是插入排序的一种,是直接针对插入排序进行改进的。它是将一个数列分成若干个数列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,在缩小增量继续排序,直到增量足够小后,再对数列整体做一次排序。
希尔排序示例:
代码示例:
private static void ShellSort(int[] number)
{
int gap;
int i = 0;
for ( gap = number.Length/2; gap >0;gap/=2) {
for (i = gap; i <number.Length; i++) {
if (number [i] < number [i - gap]) {
int temp = number [i];
int k = i - gap;
while (k >= 0&&temp < number [k]) {
number [k + gap] = number [k];
k -= gap;
}
number [k + gap] = temp;
}
}
}
归并排序
基本思想:归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
操作:首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。
时间复杂度:O(nlogn)
空间复杂度:O(n)
稳定性:稳定
示例:
代码示例:
private static void MergeSort(int[] number,int low,int high)
{
if (low < high) {
int middle = (low + high) / 2;
MergeSort (number,low,middle); //左边
MergeSort (number,middle+1,high);//右边
Merge (number,low,middle,high);
}
}
private static void Merge(int[] number,int low,int middle,int high)
{
int[] tempArry = new int[high-low+1];
int i = low;
int j = middle + 1;
int k = 0;
while (i <= middle && j <= high) {
if (number [i] < number [j]) {
tempArry [k++] = number [i++];
} else {
tempArry [k++] = number [j++];
}
}
while (i <= middle) {
tempArry [k++] = number [i++];
}
while (j <= high) {
tempArry [k++] = number [j++];
}
for (int k2 = 0; k2 < tempArry.Length; k2++) {
number [k2 + low] = tempArry [k2];
}
}
}
堆与堆排序
基本思想:堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。
在了解堆排序之前,我们先学习一下数据结构中的二叉堆。
二叉堆的定义
堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。在数组的非降序排序中,需要使用的就是大根堆,因为根据大根堆的要求可知,最大的值一定在堆顶。同理,小根堆就是每个父节点的值都小于其子节点的值,即A[PARENT[i]] <= A[i]。由此可知,最小的值一定在堆顶。
大根堆排序算法的基本操作:
①建堆,建堆是不断调整堆的过程,从len/2处开始调整,一直到第一个节点,此处len是堆中元素的个数。建堆的过程是线性的过程,从len/2到0处一直调用调整堆的过程,相当于o(h1)+o(h2)…+o(hlen/2) 其中h表示节点的深度,len/2表示节点的个数,这是一个求和的过程,结果是线性的O(n)。
②调整堆:调整堆在构建堆的过程中会用到,而且在堆排序过程中也会用到。利用的思想是比较节点i和它的孩子节点left(i),right(i),选出三者最大(或者最小)者,如果最大(小)值不是节点i而是它的一个孩子节点,那边交互节点i和该节点,然后再调用调整堆过程,这是一个递归的过程。调整堆的过程时间复杂度与堆的深度有关系,是lgn的操作,因为是沿着深度方向进行调整的。
③堆排序:堆排序是利用上面的两个过程来进行的。首先是根据元素构建堆。然后将堆的根节点取出(一般是与最后一个节点进行交换),将前面len-1个节点继续进行堆调整的过程,然后再将根节点取出,这样一直到所有节点都取出。堆排序过程的时间复杂度是O(nlgn)。因为建堆的时间复杂度是O(n)(调用一次);调整堆的时间复杂度是lgn,调用了n-1次,所以堆排序的时间复杂度是O(nlgn)
代码实现:
//构建大根堆
//当前节点i的父节点是(i-1)/2,左右子节点是2*i+1,2*i+2
public static void HeadAdjust(int[] number ,int parent,int length)
{
int temp = number [parent];
int child = 2 * parent + 1;
while (child < length) {
if (child + 1 < length && number [child] < number [child + 1])//比较左右节点的大小
child++;
if (temp >= number [child]) {
break;
}
number [parent] = number [child];
parent = child;
child = 2 * parent + 1;
}
number [parent] = temp;
}
public static int[] HeadSort(int[] number)
{
int[] tempNode=new int[number.Length];
for (int i = number.Length / 2 - 1; i >= 0; i--) {
HeadAdjust (number,i,number.Length);
}
//堆排序
for (int i = number.Length - 1; i >= 0; i--) {
int temp = number [0];
number [0] = number [i];
number [i] = temp;
tempNode [i] = temp;
HeadAdjust (number,0,i);
}
return tempNode;
}