数据结构与算法——排序算法
1 、冒泡排序
1.1 基本思想
冒泡排序的核心在于**第i次排序将将第i大的元素放置到对应的位置。**也就是说,第一次将最大的元素放置到最后的位置,第二次将第二大的元素放置到倒数第二个位置。依次类推,最后一趟是将第二小的元素放置到对应得位置。可以确定对于常规的数组,数组长度为size,那么也就需要迭代size-1趟。
那么,如何将对应的元素放到对应的位置呢?这里也就是冒泡的概念了,在每一趟的遍历的时候,我们将大的元素向后移。假设当前遍历的数组为[10,2,3,6,1],从第一个元素开始,也就是10,10比2大,10和2交换位置,10比3大,10在和3交换位置,以此类推,一直到10交换到最后的一个位置,也就是最大的元素找到了其对应的位置。
其他趟数的操作和上面类似,在此不做赘述。
1.2 代码实现
void BubbleSort(vector<int> & v)
{
//每i趟排序,就是将第i大的元素排序到对应的位置
int i, j;
for ( j = 0; j < v.size()-1; j++)
{
for (i = 0; i < v.size() - 1 - j; i++)
{
if (v[i] > v[i + 1])
{
int temp = v[i];
v[i] = v[i + 1];
v[i + 1] = temp;
}
}
}
}
1.3 代码优化
在某一趟的遍历中,如果当前数组中,没有元素进行交换位置,那么我们可以直接停止。返回结果。代码如下
void BubbleSort(vector<int> & v)
{
//每i趟排序,就是将第i大的元素排序到对应的位置
int i, j;
for ( j = 0; j < v.size()-1; j++)
{
bool flag = false;
for (i = 0; i < v.size() - 1 - j; i++)
{
if (v[i] > v[i + 1])
{
flag = true;
int temp = v[i];
v[i] = v[i + 1];
v[i + 1] = temp;
}
}
if (!flag)
return;
}
}
1.4 时间复杂度分析
对于一个长度为n的数组,需要n-1次遍历来将不同的元素放置到对应的位置,每一趟遍历n个元素。那么时间复杂度为 O ( n 2 ) O(n^2) O(n2)
2 选择排序
2.1 基本思想
第一次从数组中选择一个最小的元素,将这个元素交换到数组的0号位置,第二次选择第二小的元素,将其交换到1号位置。依次类推,一直到数组中第二大的元素被插入到倒数第二个位置,此时最大的元素肯定在其对应的最后一个位置了。
2.2 代码实现
void insertSort(vector<int> &v)
{
int i, j;
for (i = 0; i < v.size() - 1; i++)
{
int min_v = 10000;
int min_index = -1;
for (j = i; j < v.size(); j++)
{
if (v[j] < min_v)
{
min_v = v[j];
min_index = j;
}
} //找到最小值
int temp = v[min_index];
v[min_index] = v[i];
v[i] = temp;
}
}
2.3 复杂度分析
对于一个数组而言,我们首先需要跌打n-1轮来确定n-1个最小的元素,确定一个最小元素的过程也是遍历当前数组的一部分,两个位置综合,时间复杂度为 O ( n 2 ) O(n^2) O(n2)
3 插入排序
3.1 核心思想
插入排序是对欲排序的元素在数组中找到其对应的位置,达到排序的目的。其基本思想为:将n个待排序的数组看成是两个部分,一个是有序表,另外一个是无序表,每次从无序表中找到一个元素放到有序表中,直到无序表中没有元素,排序结束。
我们来举一个例子,假设数组为[2,3,5,4,1,8],我们首先以第一个元素2作为有序表,以后面的元素作为无序表,我们第一次将3在2所在的有序表中找到合适的位置,也就是2的后面。第二次将2,3作为有序表,后面的元素作为无序表,我们找到元素5在有序表中合适的位置,也就是2,3,5。后面的过程类似,一直到元素8作为无序表,其他的排好序的元素作为有序表,将8插入到有序表中。无序表此时为空,排序结束。
3.2 代码实现
void insertSort(vector<int> &v)
{
int i;
for (i = 1; i < v.size(); i++)
{
int insertVal = v[i];
int insertIndex = i-1; //从有序表的最后一个位置开始插入。
while (insertIndex >= 0 && insertVal < v[insertIndex])
{
v[insertIndex + 1] = v[insertIndex];
insertIndex--;
}
v[insertIndex + 1] = insertVal;
}
}
3.3 算法复杂度分析
初始时,无序表的大小为n-1,逐步插入到有序表中,需要 O ( n ) O(n) O(n),在插入有序表的过程中有序表中元素的移动是 O ( n ) O(n) O(n),综上所述,总的时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
4 、希尔排序
4.1 基本思想
插入排序的一种改进,主要思想为:首先对原始数据进行分组,然后对分组内的元素进行插入排序,各个分组内的元素排序好之后,再次对数据进行更大规模的分组。直到最终的分组为一,进行排序后返回结果。
假设有[0,2,4,1,5,6,7,3,8,9]
第一次分成10/2=5组,0和6,2和7,4和3,1和8,5和9分别一组。对于这五组数据进行插入,也就是按照大小调换位置。该轮插入排序结束之后,数组的顺序为:[0,2,3,1,5,6,7,4,8,9]
第二次分成5/2=2组,0,3,5,7,8一组,2,1,6,4,9一组,进行插入排序,本轮迭代之后的结果为:
[0,1,3,2,5,4,7,6,8,9]
最后一次分成2/2=1组,整个数组元素为一组,进行插入排序,本轮迭代之后的结果为:
[0,1,2,3,4,5,6,7,8,9]
4.2 代码实现
交换式:
void shellSort(vector<int> &v)
{
int i, j,temp;
for (int gap = v.size() / 2; gap > 0; gap /= 2)
{
for (i = gap; i < v.size(); i++)
{
for (j = i - gap; j >= 0; j -= gap)
{
if (v[j] > v[j+gap])
{
temp = v[j];
v[j] = v[j + gap];
v[j + gap] = temp;
}
}
}
}
}
位移式:
void shellSort2(vector<int> &v)
{
for (int gap = v.size() / 2; gap > 0; gap /= 2)
{
for (int i = gap; i < v.size(); i++)
{
int j = i;
int temp = v[j];
if (v[j] < v[j - gap])
{
while (j - gap >= 0 && temp < v[j - gap])
{
v[j] = v[j - gap];
j -= gap;
}
v[j] = temp;
}
}
}
}
4.3 时间复杂度分析
O ( n 1.3 — 2 ) O(n^{1.3—2}) O(n1.3—2)
5 快速排序
5.1 基本思想
通过一趟排序,以某一个元素为基准,将待排序的数据分割成两个部分,前一个部分的元素要比后一个部分的元素的值要小。然后将该基准元素插入到对应的位置。然后在对两个部分进行排序。
5.2 代码实现
int parttion(vector<int> & v,int left,int right)
{
int l = left;
int r = right;
int temp = v[left];
while (l < r)
{
while (l < r && v[r] >= temp)
r--;
if (l < r)
v[l++] = v[r];
while (l < r &&v[l] <= temp)
{
l++;
}
if (l < r)
{
v[r--] = v[l];
}
}
v[l] = temp;
return l;
}
void quickSort(vector<int> &v, int left, int right)
{
if (left > right)
return;
int i = parttion(v, left, right);
quickSort(v, left, i - 1);
quickSort(v, i + 1, right);
}
5.3 时间复杂度分析
递归过程为
O
(
l
o
g
n
)
O(log_n)
O(logn),递归过程中的交换遍历为
O
(
n
)
O(n)
O(n),所以总的时间复杂度为:
O
(
n
l
o
g
n
)
O(nlog_n)
O(nlogn)