排序
冒泡排序
-
冒泡排序就是每次比较两个元素;
-
冒泡排序对 n 个数需要 O(n^2) 的比较次数;
-
对于 n 比较大的话,效率会非常的低,但是稳定,适用于教学。
图片演示
void bubbleSort(int a[], int len)
{
int i, j;
int temp; //临时值
for (i = 0; i < len; i++)
{
for (j = 0; j < len - i - 1; j++) //内层循环将最大的排在最右边, len-i-1 与 j+1相对应
{
if (a[j] > a[j + 1]) //从小到大;< 是从大到小排序
{
temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
}
经过搜索,稳定性:
稳定性是指如果存在多个具有相同排序码的记录,经过排序后,这些记录的相对次序仍然保持不变,则这种排序算法称为稳定的。
选择排序
1.工作原理
在未排序序列中找到最小(大)的元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)的元素,存放到已排序序列的末尾,直到所有元素排序完。
2.对于 n 个元素的时间复杂度为 O(n^2);
3.n比较大的话,效率也会与冒泡排序类似,不稳定。
void seletSort(int a[], int len)
{
int i, j, k;
for (i = 0; i < len; i++)
{
k = i; //初始化最小的标记
for (j = i; j < len; j++)
{
if (a[j] < a[k])
{
k = j;
}
}
int temp = a[i]; //交换值
a[i] = a[k];
a[k] = temp;
}
}
插入排序
- 工作原理
-
在数组中,(先假设第一个元素为已知排序),对于未知排序的部分(一次处理一个元素),在已知排序中从后向前扫描,找到相应的位置插入,把已排序元素逐步向后移位,直到所有元素排序完;
-
就比如打扑克牌,按常理来说,牌都是会从小到大在手上,{3,7,9,5,1},第一张 3 先拿在手上;第二张 7 放在 3 的右边;第三张 9 比前两张大 放在 7 的右边; 第四张比 9 、7 大,比 3 小,放在 3 的右边,后两张的位置往右要挪一挪;第五张 1 同理,放在 3 的前面。
-
对于 n 个元素的时间复杂度为 O(n^2);
-
n比较大的话,效率也会与冒泡排序类似,稳定,在 n 较小时首选插入排序。
void insertSort(int a[], int len) { int i,j,key; for (i = 1; i < len; i++) { key = a[i];//需要插入的值 j = i - 1; while ((j >= 0) && (a[j] > key)) //判断已排序的部分 { a[j + 1] = a[j];//向后挪动 j--; } a[j + 1] = key;//将值插入j+1的位置 } }
不过对于上述的简单排序,在c++中可以直接用 sort
函数取代
快速排序
- 工作原理
- 在数组中选一个元素作为基准值(中间值,一般以第一个元素为准),将数组分成两部分,小于基准值的在左边,大于基准值的在右边,然后再分别对这两部分进行同样的操作,直到数组不能再分为止。
/*void quickSort(int a[], int l, int r)
{
if (l < r)
{
int pos = Partition(a, l, r);
quickSort(a, l, pos - 1);
quickSort(a, pos + 1, r);
}
}
int Partition(int a[], int l, int r)
{
int p = (round(1.0*rand()/RAND_MAX*(r - l) + l));//随机快排
swap(a[p], a[l]);
int key = a[l];
while (l < r)
{
while (l < r && a[r] > key)
{
r--;
}
a[l] = a[r];
while (l < r && a[l] <= key)
{
l++;
}
a[r] = a[l];
}
a[i] = key;
return l;
}
*/
void quickSort(int a[], int l, int r)
{
if (l >= r)
{
return;
}
int i = l;
int j = r;
int key = a[l]; //第一个数为基准值key
while (i < j) //当l=r时退出循环
{
while (i < j && a[j] >= key)
{
j--;
}
a[i] = a[j];
while (i < j && a[i] < key) //此位置的r和l不能反,否则基准值的位置将错过
{
i++;
}
a[j] = a[i];
}
a[i] = key;
quickSort(a, l, i - 1);//递归调用基准值的左半部分
quickSort(a, i + 1, r);//递归调用基准值的右半部分
}
-
对于n个元素的时间复杂度为O(nlog2n);
-
借用一下别人的话
-
若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜;
-
快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短。
归并排序
- 工作原理
-
将数组对半分,直到每个数组只有一个元素为止;
-
将分出来的部分重新按顺序合并。
void merge(int a[], int l1, int r1, int l2, int r2)
{
int i = l1, j = l2;
int temp[6], flag = 0; //这里的6不是固定值,temp临时存放数组,flag表示下标
while (i <= r1 && j <= r2) //合并两个序列
{
if (a[i] <= a[j])
{
temp[flag++] = a[i++];
}
else
{
temp[flag++] = a[j++];
}
}
while (i <= r1)
{
temp[flag++] = a[i++]; //将【l1,r1】的剩余元素存放到temp中
}
while (j <= r2)
{
temp[flag++] = a[j++]; 将【l2,r2】的剩余元素存放到temp中
}
for (i = 0; i < flag; i++)
{
a[l1 + i] = temp[i]; // 将合并后的序列赋值给a数组
}
}
void mergeSort(int a[], int l, int r)
{
if (l < r)//l=r时退出
{
int mid = (l + r) / 2;
mergeSort(a, l, mid); //将左半部分归并
mergeSort(a, mid + 1, r);//将有半部分归并
merge(a, l, mid, mid + 1, r);//将左子区间和右子区间合并
}
}
-
对于n个元素的时间复杂度为O(nlog2n);
-
若 n 较大,要求排序稳定,可用归并排序。
希尔排序
- 工作原理
-
希尔排序中每次都要确定其步长,直到步长为 1 的所有子序列进行最后一次插入排序即可完成,步长也就是原本数组长度的一半(也就是步长),每次在一半的基础上再求一半,直到步长为 1,每次算出步长都要进行一次插入排序;
-
换个说法也就是,每次以步长为列转换为二维数组,对数组的每一列进行插入排序,再将二维数组转换为序列,同理,当步长为 1 时,也可以将其转换为只有一列的数组,对该列进行插入排序,最后结果就会出现。
void shellsort(int a[], int len) { int i, j, gap; //gap表示步长 for (gap = len / 2; gap > 0; gap /= 2) //每次步长减小 { for (i = gap; i < len; i++) //保证每次步长区分后,每个元素能进行排序 { for (j = i - gap; j >= 0 && a[j] > a[j + gap]; j -= gap)//对应的元素进行排序 swap(a[j], a[j + gap]); } } }
-
希尔排序是对插入排序的一种优化(官方语言),是非稳定排序算法;
-
其时间复杂度是根据步长的序列不同而不同(比较复杂),引用一下别人的话:它的运行时间不到平方级别,也即时间复杂度小于O(n^2)。
计数排序
- 工作原理
- 找出数组中最大的元素,存放到额外的数组中且该数组长度要加一个长度(个人认为避免溢出),然后统计数组中每个为 i 的元素出现的次数,且次数进行累加,最后再反向填充目标数组每存放一个元素 i ,次数减一。
int maxArr(int *a, int len) //返回数组中最大的元素
{
int i, max;
max = a[0];
i = 1;
int *p;
p = a + 1; //此时的p指向a[1]
while (i++ < len)
{
if (max < *p)
{
max = *p;
}
p++; //p指向下一个数组
}
return max;
}
int *countingSort(int *a, int len, int k) //对其进行计数排序
{
int c[k + 1], i, value, pos;
int *b = (int *)malloc(len * sizeof(int));
for (i = 0; i <= k; i++)
{
c[i] = 0;
}
for (i = 0; i < len; i++)
{
c[a[i]]++; //对数组a里的元素计数
}
for (i = 1; i <= k; i++)
{
c[i] = c[i] + c[i - 1]; //计数进行累加
}
for (i = len - 1; i >= 0; i--)
{
value = a[i];
pos = c[value];
b[pos - 1] = value;
c[value]--;
}
return b;
}
int main()
{
int a[] = {3,5,2,1,2,3,0,6};
int len, max;
len = sizeof(a) / sizeof(a[0]);
max = maxArr(a, len);
int *p = countingSort(a, len, max);
int k;
for (k = 0; k < len; k++)
{
printf("%d ", p[k]);
}
printf("\n");
free(p);
return 0;
}
-
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。(借鉴高人的原话)
-
计数排序是一种非常快捷的稳定性强的排序方法,时间复杂度O(n+k),其中n为要排序的数的个数,k为要排序的数的组大值。计数排序对一定量的整数排序时候的速度非常快,一般快于其他排序算法。但计数排序局限性比较大,只限于对整数进行排序。计数排序是消耗空间发杂度来获取快捷的排序方法,其空间发展度为O(K)同理K为要排序的最大值。(借鉴一下高级解释)
桶排序
- 工作原理
- 桶排序是计数排序的升级版。将原数组中的元素分配到有限数量的桶里,每个桶里的元素再分别排序(寻找合适的排序方法),最后将桶合并,有序的输出元素。
/*
比如 a[] = {55, 38, 64, 95, 74, 28, 38}
分十个桶分别是[0~9],[10~19],[20~29]....[90~99]
以下代码是参考别人的
*/
typedef struct node
{
int key;
struct node *next;
}keynode;
void bucket_sort(int a[], int len, int bucket_size)
{
int i,j;
keynode **bucket_table = (keynode **)malloc(bucket_size * sizeof(keynode *)); //申请分桶的结点
for (i = 0; i < bucket_size; i++) //初始化十个桶
{
bucket_table[i] = (keynode *)malloc(sizeof(keynode));//为每个桶申请空间存放能进桶的物品
bucket_table[i]->key = 0; //用来统计每个桶装的物品的个数
bucket_table[i]->next = NULL;
}
for (j = 0; j < len; j++)
{
keynode *node = (keynode *)malloc(sizeof(keynode)); //申请临时结点
node->key = a[j];
node->next = NULL;
int index = a[j] / 10; //将数组a[]里的元素简化
keynode *p = bucket_table[index]; //简化后的元素可以作为10个桶之一的下标
if (0 == p->key)//如果桶里是空的
{
bucket_table[index]->next = node; //将数组a[]里的元素存到桶里
(bucket_table[index]->key)++;
}
else
{
while (p->next != NULL && p->next->key <= node->key)//桶里面的物品都是有顺序的
{
p = p->next;
}
node->next = p->next;
p->next = node;
(bucket_table[index]->key)++;
}
}
keynode *k = NULL;
for (i = 0; i < bucket_size; i++)//把桶里的东西有顺序的取出
{
for (k = bucket_table[i]->next; k != NULL; k = k->next)
{
printf("%d ", k->key);
}
}
printf("\n");
}
int main()
{
int a[] = {55, 38, 64, 95, 74, 28, 38};
int len = sizeof(a)/sizeof(int);
bucket_sort(a, len, 10);
return 0;
}
- 桶排序是比较稳定的排序,其时间复杂度一般O(n+k)
若有不妥之处,希望能指点指点