先复习下常用的基础的排序算法,主要有冒泡排序、插入排序、选择排序、希尔排序、归并排序、快速排序、堆排序。本文记录基本的算法思想和C++实现代码,并记录各算法的时间和空间复杂度。
本文内容如有错误,请各位大侠帮忙指出啊,多谢~
1,冒泡排序
1)基本思想
一个数组,分前面的无序段和后面的有序段,每次循环把无序段的最大/小的元素移到有序段的开头。
注:本文都实现从小到大排序
2)代码实现
void swap(int &a, int &b)
{
int tmp = a;
a = b;
b = tmp;
}
int bubble_sort(int array[], int len)
{
if (NULL == array || len <= 0)
{
return FALSE;
}
int i = 0;
int j = 0;
for (i = 0; i < len; ++i)
{
for (j = 0; j < len - 1 - i; ++j)
{
if (array[j] > array[j + 1])
{
swap(array[j], array[j + 1]);
}
}
}
return TRUE;
}
3)时间复杂度,平均O(n2)(注,是n的平方,下同);空间复杂度,O(1)
2,插入排序
1)基本思想
一个数组,分前面的有序段和后面的无序段,每次循环把无序段的首元素插入到有序段的相应位置,此时要保存有序段的有序性。
2)代码实现
int insert_sort(int array[], int len)
{
if (NULL == array || len <= 0)
{
return FALSE;
}
int i = 0;
int j = 0;
int tmp = 0;
for (i = 1; i < len; ++i)
{
tmp = array[i];
for (j = i; (j > 0) && (array[j - 1] > tmp); --j)
{
array[j] = array[j - 1];
}
array[j] = tmp;
}
return TRUE;
}
3)时间复杂度,平均O(n2),空间复杂度O(1)
3,选择排序
1)基本思想
一个数组,分前面的有序段和后面的无序段,每次循环把无序段中的最小/大值放到有序段的最后面。
2)代码实现
void select_sort(int array[], int len)
{
if (NULL == array || len <= 0)
{
return FALSE;
}
int i = 0;
int j = 0;
int min;
int pos;
for (i = 0; i < len - 1; ++i)
{
min = array[i];
pos = i;
for (j = i + 1; j < len; ++j)
{
if (array[j] < min)
{
min = array[j];
pos = j;
}
}
if (pos != i)
{
swap(array[i], array[pos]);
}
}
return TRUE;
}
3)时间复杂度,平均O(n2),空间复杂度,O(1)
4,希尔排序
1)基本思想
希尔排序也叫缩小增量排序,与插入排序思想类似,将数组中的数插入到其前面的数组中,不同的地方是,每次按一定间隔来比较两个元素,该间隔逐渐减少到相邻两元素的间隔;该思路可以进行长距离的元素交换,最终可以减少总的元素交换次数,其效率比插入排序高些;具体看下代码就清楚了。
2)代码实现
int shell_sort(int array[], int len)
{
if (NULL == array || len <= 0)
{
return FALSE;
}
int increment = len >> 2;
int i = 0;
int j = 0;
int tmp = 0;
for (; increment >= 1; increment /= 2)
{
for (i = increment; i < len; ++i)
{
tmp = array[i];
for (j = i; j >= increment && array[j - increment] >= tmp; j -= increment)
{
array[j] = array[j - increment];
}
array[j] = tmp;
}
}
return TRUE;
}
3)时间复杂度,平均O(n2),空间复杂度,O(1)
5,归并排序
1)基本思想
前面的几种排序算法都是简单算法,平均时间复杂度大,下面的几种排序算法则在时间复杂度上有所改进。
归并排序的思想是,运用递归的思想,先假设数组前半部分和后半部分各自有序,然后把前后部分有序的合并在一起;此时前提是数组的前半部分和后半部分是有序的,因此分别使用前面一样的思想,对各半部分继续之前的处理;一直递归下去,直到各数组只有一个元素为止。
2)代码实现
int merge(int array[], int tmp[], int first, int mid, int last)
{
if (NULL == array || NULL == tmp || first > mid || mid > last)
{
return FALSE;
}
if (first == last)
{
return TRUE;
}
int front_i = first;
int front_end = mid;
int later_i = mid + 1;
int later_end = last;
int k = first;
while(front_i <= front_end && later_i <= later_end)
{
if (array[front_i] <= array[later_i])
{
tmp[k++] = array[front_i++];
}
else
{
tmp[k++] = array[later_i++];
}
}
while (front_i <= front_end)
{
tmp[k++] = array[front_i++];
}
while (later_i <= later_end)
{
tmp[k++] = array[later_i++];
}
for (int k2 = first; k2 <= last; ++k2)
{
array[k2] = tmp[k2];
}
return TRUE;
}
int m_sort(int array[], int tmp[], int first, int last)
{
if (NULL == array || NULL == tmp)
{
return FALSE;
}
if (first >= last)
{
return FALSE;
}
int mid = (first + last) / 2;
m_sort(array, tmp, first, mid);
m_sort(array, tmp, mid + 1, last);
/* merge two ordered array */
return merge(array, tmp, first, mid, last);
}
int merge_sort(int array[], int len)
{
if (NULL == array || len <= 0)
{
return FALSE;
}
int ret;
int *tmp_array = new int[len];
ret = m_sort(array, tmp_array, 0, len - 1);
delete [] tmp_array;
return ret;
}
3)时间复杂度,平均O(n*log2n)(注,是以2为底,下同),空间复杂度O(n)
6,快速排序
1)基本思想
一个数组,选择其中一个元素作为哨兵,使得一个循环后,原始数组变为三部分,第一部分元素值都小于哨兵元素,第二部分为哨兵元素,第三部分元素值都大于哨兵元素;随后迭代,对第一部分和第二部分依然通过前面的思想进行处理,直到要处理的部分,元素个数为1。
需要注意的一点是哨兵的选取,选取的哨兵值最好是要处理的当前部分数组中的中间值;否则若简单的选择第一个元素作为哨兵,而要排序的数组又是有序的,则通过快排思想将花费最大的O(n2)的时间复杂度;因此我们采取一个方法是,选择数组第一个元素、中间元素、最优元素的中间值作为哨兵。
2)代码实现
int select_flag(int array[], int first, int last)
{
if (NULL == array || first >= last)
{
return FALSE;
}
int mid = (first + last) / 2;
if ((array[first] >= array[mid] && array[first] <= array[last])
|| (array[first] <= array[mid] && array[first] >= array[last]))
{
return TRUE;
}
else if ((array[mid] >= array[first] && array[mid] <= array[last])
|| (array[mid] <= array[first] && array[mid] >= array[last]))
{
swap(array[mid], array[first]); // swap define in bubble_sort
}
else
{
swap(array[last], array[first]);
}
return TRUE;
}
int q_sort(int array[], int first, int last)
{
if (NULL == array)
{
return FALSE;
}
if (first == last)
{
return TRUE;
}
int ret;
/* select the flag element, after the func, first element will be the flag */
ret = select_flag(array, first, last);
if (TRUE != ret)
{
return ret;
}
int i = first + 1;
int j = last;
int flag = array[first];
while (i <= j)
{
while (array[i] <= flag && i <= j) {++i;}
while (array[j] >= flag && i <= j) {--j;}
if (i <= j)
{
swap(array[i], array[j]);
}
}
if (first != j)
{
swap(array[first], array[j]);
}
q_sort(array, first, j - 1);
q_sort(array, j + 1, last);
return TRUE;
}
int quick_sort(int array[], int len)
{
if (NULL == array || len <= 0)
{
return FALSE;
}
return q_sort(array, 0, len - 1);
}
3)时间复杂度,平均O(nlog2n), 空间复杂度O(1);
7,堆排序
1)基本思想
堆是一棵完全二叉树,且每个节点的值大于左右子节点的值。堆排序的算法就是,先将原始数组构造为一个最大/小堆;随后每次取堆的根节点,并维护剩余节点的堆性质;这样按这种顺序取出来的元素就是有序的了。看代码实现就清楚了。
2)实现代码
#define CHILD(i) (2*(i) + 1)
int perc_down(int array[], int pos, int len)
{
if (NULL == array || pos < 0 || pos >= len || len <=0)
{
return FALSE;
}
int i;
int child;
int flag = array[pos];
for (i = pos; CHILD(i) <= len - 1; i = child)
{
child = CHILD(i);
if ((child != len - 1) && (array[child] < array[child + 1]))
{
++child;
}
if (array[child] > flag)
{
array[i] = array[child];
}
else
{
break;
}
}
array[i] = flag;
return TRUE;
}
int heap_sort(int array[], int len)
{
if (NULL == array || len <= 0)
{
return FALSE;
}
int i;
/* build heap */
for (i = len / 2; i >= 0; --i)
{
perc_down(array, i, len);
}
/* mv the max element in root to the last position */
for (i = len - 1; i >= 1; --i)
{
swap(array[0], array[i]);
perc_down(array, 0, i);
}
return TRUE;
}
3)时间复杂度,O(nlog2n),空间复杂度O(1)