前言
秋招已经开启一个月了,九月份和十月份是我们秋招找工作的黄金时期。但是大多数的企业都会在笔试或者面试中会考察我们数据结构的知识点,有的会直接考查数据结构的代码实现,比如说树的三种深度优先遍历代码递归以及迭代方式的实现。其次就是考察最多的就是几种排序和常用的查找方法,需要我们熟练掌握。还有的就是会通过一个实际问题来对相关的算法进行考察,比如说动态规划、穷举法、贪心算法等。前一篇文章给大家介绍了数据结构中帮助大家理解代码的执行过程和相应算法的工具。本文给大家介绍数据结构中最为重要也是面试中经常考察的算法之一——排序算法的总结。
十大排序算法实现
在数据结构与算法中,我们会学到十大算法,他们分别是冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序、计数排序、桶排序、基数排序。我们在任何时候,都应该要写出相关的代码,只有这样,我们才能够在写其他算法中更好的应用。由于该算法比较基础,也是比较重要的,接下来,我们通过C语言、java和python三种流行语言将其算法进行实现。接下来给大家介绍第一种排序——冒泡排序的实现。
1、冒泡排序
冒泡排序的原理在于要么元素上浮,从右(大)往左(小)依次确认,相邻元素间,如果右小于左,则交换,每次确认一位最小数。要么元素下沉,从左(小)往右(大)依次确认,相邻元素间,如果左大于右,则交换,每次确认一位最大数。我们用动图解释如下:
接下来我们分别用C语言、java和python3三种语言将其实现:
1、我们用C语言将其实现,代码如下:
#include<stdio.h>
void BubbleSort(int k[],int n){
int i,j,temp,count1=0,count2=0,flag;
flag=1;
for(i=0;i<n-1 && flag;i++){
for(j=n-1;j>i;j--){
count1++;
flag=0;
if(k[j-1]>k[j]){
count2++;
temp=k[j-1];
k[j-1]=k[j];
k[j]=temp;
flag=1;
}
}
}
printf("总过进行了%d次比较,进行了%d次移动!!",count1,count2);
}
int main(){
int i,a[10]={1,0,2,3,4,5,6,7,8,9};
BubbleSort(a,10);
printf("排序后的结果是: \n");
for(i=0;i<10;i++){
printf("%d ",a[i]);
}
printf("\n\n");
return 0;
}
排序效果如下图所示:
2、我们用java语言将其实现,代码如下:
package 数据结构.排序;
public class 冒泡排序 {
public static void bubbleSort(int [] a, int n){
int j , k;
int flag = n ;
//flag来记录最后交换的位置,也就是排序的尾边界
while (flag > 0){
//排序未结束标志
k = flag;
//k 来记录遍历的尾边界
flag = 0;
for(j=1; j<k; j++){
if(a[j-1] > a[j]){
//前面的数字大于后面的数字就交换
//交换a[j-1]和a[j]
int temp;
temp = a[j-1];
a[j-1] = a[j];
a[j]=temp;
flag = j;
}
}
}
}
public static void main(String[] args) {
int[] arr = {2,1,5,4,3,8,7};
冒泡排序.bubbleSort(arr, arr.length);
for(int i:arr){
System.out.print(i+"\t");
}
}
}
排序效果如下图所示:
3、我们用python语言将其实现,代码如下:
def bubble_sort(alist):
n = len(alist)
for j in range(n-1):
count = 0
for i in range(0,n-1-j):
if alist[i]>alist[i+1]:
alist[i],alist[i+1] = alist[i+1],alist[i]
count+=1
if count == 0:
return
if __name__ == '__main__':
li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
print("排序之前为:", li)
bubble_sort(li)
print("排序之后为:",li)
排序效果如下图所示:
2、选择排序
选择排序的原理在于要么拿未确认的最左位元素,依次和右边元素比较,如果右边元素更小,则交换。要么拿未确认的最右位元素,依次和左边元素比较,如果左边元素更大,则交换。我们用动态图解释如下:
接下来我们分别用C语言、java和python3三种语言将其实现:
1、我们用C语言将其实现,代码如下:
#include<stdio.h>
void SelectSort(int k[],int n){
int i,j,min,temp,count1=0,count2=0;
for(i=0;i<n-1;i++){
min=i;
for(j=i+1;j<n;j++){
count1++;
if(k[j]<k[min]){
min=j;
}
}
if(min!=i){
count2++;
temp=k[min];
k[min]=k[i];
k[i]=temp;
}
}
printf("总共进行了%d次比较,进行了%d次移动!!",count1,count2);
}
int main(){
int i,a[10]={5,2,8,4,9,3,1,7,0,6};
SelectSort(a,10);
printf("排序后的结果是: \n");
for(i=0;i<10;i++){
printf("%d ",a[i]);
}
printf("\n\n");
return 0;
}
效果图如图所示:
2、我们用java语言将其实现,代码如下:
package 数据结构.排序;
public class 选择排序 {
public static void main(String[] args) {
int[] arr = {52, 63, 14, 59, 68, 35, 8, 67, 45, 90};
System.out.println ("原数组:");
for (int i:arr){
System.out.print(i+" ");
}
System.out.println ();
selectSort(arr);
System.out.println ("排序后");
for (int i:arr){
System.out.print(i+" ");
}
}
public static void selectSort(int[] arr){
for (int i =0; i < arr.length-1; i++){
int min = i;
for (int j = i+1; j < arr.length;j++){
if(arr[j] < arr[min]){
min = j;
}
}
if(min != i){
swap(arr, i, min);
}
}
}
public static void swap(int[] arr, int a, int b){
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}
排序效果如下图所示:
3、我们用python语言将其实现,代码如下:
def select_sort(alist):
min = 0
n = len(alist)
for j in range(n-1):
min_index=j
for i in range(j+1, n):
if alist[min_index] > alist[i]:
min_index = i
alist[j],alist[min_index] = alist[min_index],alist[j]
if __name__ == '__main__':
li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
print("排序之前为:", li)
select_sort(li)
print("排序之后为:",li)
排序效果如下图所示:
3、插入排序
插入排序的原理在于:将某一元素插入已经排序好的数组中,元素位于数组右方,则元素依次与左方比较,找出合适位置,被比较元素后移一位,该元素前置,要么元素位于数组左方,则元素依次与右方比较,找出合适位置,被比较元素前移一位,该元素后置。我们用动图解释如下:
接下来我们分别用C语言、java和python3三种语言将其实现:
1、我们用C语言将其实现,代码如下:
#include <stdio.h>
//自定义的输出函数
void print(int a[], int n ,int i){
printf("%d:",i);
for(int j=0; j<8; j++){
printf("%d",a[j]);
}
printf("\n");
}
//直接插入排序函数
void InsertSort(int a[], int n)
{
for(int i= 1; i<n; i++){
if(a[i] < a[i-1]){//若第 i 个元素大于 i-1 元素则直接插入;反之,需要找到适当的插入位置后在插入。
int j= i-1;
int x = a[i];
while(j>-1 && x < a[j]){ //采用顺序查找方式找到插入的位置,在查找的同时,将数组中的元素进行后移操作,给插入元素腾出空间
a[j+1] = a[j];
j--;
}
a[j+1] = x; //插入到正确位置
}
print(a,n,i);//打印每次排序后的结果
}
}
int main(){
int a[8] = {3,1,7,5,2,4,9,6};
InsertSort(a,8);
return 0;
}
排序效果如下图所示:
2、我们用java语言将其实现,代码如下:
package 数据结构.排序;
public class 插入排序 {
public static void main(String[] args) {
int[] ins = {2,3,5,1,23,6,78,34};
int[] ins2 = InsetSort(ins);
for (int in:ins2){
System.out.println (in);
}
}
public static int[] InsetSort(int[] ins){
for (int i = 1; i < ins.length; i++){
int temp = ins[i];
int j;
for (j = i; j >0 && ins[j-1]>temp; j--){
ins[j] = ins[j-1];
}
ins[j] = temp;
}
return ins;
}
}
排序效果如下图所示:
3、我们用python语言将其实现,代码如下:
def insert_sort(alist):
n = len(alist)
for j in range(1, n):
i = j
while i > 0:
if alist[i] < alist[i-1]:
alist[i], alist[i-1] = alist[i-1],alist[i]
i-=1
else:
break
if __name__ == '__main__':
li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
print("排序之前为:", li)
insert_sort(li)
print("排序之后为:",li)
排序效果如下图所示:
4、希尔排序
希尔排序的原理在于按照一定间隔(记d)执行插入排序,要么元素位于数组右方,记索引为x,则元素依次与左方x-d的元素比较,找出合适位置,被比较元素后移一位,该元素前置。要么元素位于数组左方,记索引为x,则元素依次与右方x+d的元素比较,找出合适位置,被比较元素前移一位,该元素后置。我们用动图解释如下:
接下来我们分别用C语言、java和python3三种语言将其实现:
1、我们用C语言将其实现,代码如下:
#include <stdio.h>
void ShellSort(int k[],int n){
int i,j,temp;
int gap=n;
do
{
gap=gap/3+1;
for(i=gap;i<n;i++){
if(k[i]<k[i-gap]){
temp=k[i];
for(j=i-gap;k[j]>temp;j-=gap){
k[j+gap]=k[j];
}
k[j+gap]=temp;
}
}
}while(gap>1);
}
int main(){
int i,a[10]={5,2,6,0,3,9,1,7,4,8};
ShellSort(a,10);
printf("排序后的结果是: \n");
for(i=0;i<10;i++){
printf("%d ",a[i]);
}
printf("\n\n");
return 0;
}
排序效果如下图所示:
2、我们用java语言将其实现,代码如下:
package 数据结构.排序;
public class 希尔排序 {
public static void main(String[] args) {
int[] arr = {5, 1, 7, 3, 1, 6, 9, 4};
shellSort(arr);
for (int i : arr) {
System.out.print(i + "\t");
}
}
private static void shellSort(int[] arr) {
//step:步长
for (int step = arr.length / 2; step > 0; step /= 2) {
//对一个步长区间进行比较 [step,arr.length)
for (int i = step; i < arr.length; i++) {
int value = arr[i];
int j;
//对步长区间中具体的元素进行比较
for (j = i - step; j >= 0 && arr[j] > value; j -= step) {
//j为左区间的取值,j+step为右区间与左区间的对应值。
arr[j + step] = arr[j];
}
//此时step为一个负数,[j + step]为左区间上的初始交换值
arr[j + step] = value;
}
}
}
}
排序效果如下图所示:
3、我们用python语言将其实现,代码如下:
def shell_sort(alist):
n = len(alist)
gap = n // 2
while gap > 0:
for j in range(gap, n):
i = j
while i > 0:
if alist[i] < alist[i-gap]:
alist[i], alist[i-gap] = alist[i-gap],alist[i]
i-=gap
else:
break
gap //= 2
if __name__ == '__main__':
li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
print("排序之前为:", li)
shell_sort(li)
print("排序之后为:",li)
排序效果如下图所示:
5、归并排序
归并排序的原理在于采用分而治之的思想:将数组等分为若干子数组,对每个子数组排序,然后再合并有序的子数组。我们用动图解释如下:
接下来我们分别用C语言、java和python3三种语言将其实现:
1、这里需要注意的是:归并排序分为递归和迭代两种方法实现,我们用C语言将其实现,代码如下:
①、迭代版实现:
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 10
void MergeSort(int k[], int n)
{
int i, next, left_min, left_max, right_min, right_max;
int *temp = (int *)malloc(n * sizeof(int));
for( i=1; i < n; i*=2 ) //i 为步长, 1, 2, 4, 8...
{
for( left_min=0; left_min < n-i; left_min = right_max )//从0开始,根据步长,向右分组排序
{
right_min = left_max = left_min + i;
right_max = left_max + i;
if( right_max > n )
{
right_max = n;
}
next = 0;
//将k[]数组中小的数据备份到temp[]数组中,
//结束条件是left部分或者right部分有一个已经完全拷贝到temp[]中
while( left_min < left_max && right_min < right_max )
{
if( k[left_min] < k[right_min] )
{
temp[next++] = k[left_min++];
}
else
{
temp[next++] = k[right_min++];
}
}
//现在k[]数组中没有备份的都是大的数据,有两种可能性,
//一种是left部分拷贝完成,另一种可能性是right部分拷贝完成,对于后一种情况,
//无需考虑,因为right部分本来就排在k[]数组中靠后的位置,
//也就是存放大数据的位置,这里只需要考虑第一种情况,将left部分的较大的数据,从k[left_max],开始,
//覆盖到k[right_min]的位置(k[right_min]中的数据上一部已经拷贝到temp[]数组中)
while( left_min < left_max )
{
k[--right_min] = k[--left_max];
}
while( next > 0 )//,将temp[]数组中备份的数据重新拷贝回k[]数组中
{
k[--right_min] = temp[--next];
}
}
}
}
int main()
{
int i, a[10] = {5, 2, 6, 0, 3, 9, 1, 7, 4, 8};
MergeSort(a, 10);
printf("排序后的结果是:\n");
for( i=0; i < 10; i++ )
{
printf("%d ", a[i]);
}
printf("\n\n");
return 0;
}
排序效果如下图所示:
②、递归版实现如下:
#include<stdio.h>
#define MAXSIZE 10
void merging(int *list1,int list1_size,int *list2,int list2_size){
int temp[MAXSIZE];
int i,j,k,m;
i=j=k=0;
while(i<list1_size&&j<list2_size){
if(list1[i]<list2[j]){
temp[k++]=list1[i++];
}else{
temp[k++]=list2[j++];
}
}
while(i<list1_size){
temp[k++]=list1[i++];
}
while(j<list2_size){
temp[k++]=list2[j++];
}
for(m=0;m<list1_size+list2_size;m++){
list1[m]=temp[m];
}
}
void MergeSort(int k[],int n){
if (n>1){
int *list1=k;
int list1_size=n/2;
int *list2=k+n/2;
int list2_size=n-n/2;
MergeSort(list1,list1_size);
MergeSort(list2,list2_size);
merging(list1,list1_size,list2,list2_size);
}
}
int main(){
int i,a[10]={5,2,8,4,9,3,1,7,0,6};
MergeSort(a,10);
printf("排序后的结果是: \n");
for(i=0;i<10;i++){
printf("%d ",a[i]);
}
printf("\n\n");
return 0;
}
排序效果如下图所示:
2、我们用java语言将其实现,代码如下:
package 数据结构.排序;
public class 归并排序 {
public static void main(String[] args) {
int[] arr = {11,44,23,67,88,65,34,48,9,12};
int[] tmp = new int[arr.length]; //新建一个临时数组存放
mergeSort(arr,0,arr.length-1,tmp);
for(int i=0;i<arr.length;i++){
System.out.print(arr[i]+" ");
}
}
public static void merge(int[] arr,int low,int mid,int high,int[] tmp){
int i = 0;
int j = low,k = mid+1; //左边序列和右边序列起始索引
while(j <= mid && k <= high){
if(arr[j] < arr[k]){
tmp[i++] = arr[j++];
}else{
tmp[i++] = arr[k++];
}
}
//若左边序列还有剩余,则将其全部拷贝进tmp[]中
while(j <= mid){
tmp[i++] = arr[j++];
}
while(k <= high){
tmp[i++] = arr[k++];
}
for(int t=0;t<i;t++){
arr[low+t] = tmp[t];
}
}
public static void mergeSort(int[] arr,int low,int high,int[] tmp){
if(low<high){
int mid = (low+high)/2;
mergeSort(arr,low,mid,tmp); //对左边序列进行归并排序
mergeSort(arr,mid+1,high,tmp); //对右边序列进行归并排序
merge(arr,low,mid,high,tmp); //合并两个有序序列
}
}
}
排序效果如下图所示:
3、我们用python语言将其实现,代码如下:
def merge_sort(alist):
n = len(alist)
if n <= 1:
return alist
mid = n // 2
left_li = merge_sort(alist[:mid])
right_li = merge_sort(alist[mid:])
left_pointer = 0
right_pointer = 0
result = []
while left_pointer < len(left_li) and right_pointer < len(right_li):
if left_li[left_pointer] < right_li[right_pointer]:
result.append(left_li[left_pointer])
left_pointer+=1
else:
result.append(right_li[right_pointer])
right_pointer+=1
result += left_li[left_pointer:]
result += right_li[right_pointer:]
return result
if __name__ == '__main__':
li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
print("排序之前为:", li)
sorted_li = merge_sort(li)
print("排序一次:", li)
print("排序之后为:",sorted_li)
排序效果如下图所示:
6、快速排序
快速排序的原理在于采用分而治之的思想:
- 第一步、选取基准值,小于基准值的在左侧作为子数组,大于基准值的在右侧作为子数组,
- 第二步、再对两个子数组重复上一步操作
- 第三步、合并所有子数组
我们用动图解释如下:
接下来我们分别用C语言、java和python3三种语言将其实现:
1、我们用C语言将其实现,代码如下:
#include <stdio.h>
#define MAX_LENGTH_INSERT_SORT 7
void swap(int k[],int low,int high){
int temp;
temp=k[low];
k[low]=k[high];
k[high]=temp;
}
void ISort(int k[],int n){
int i,j,temp;
for(i=1;i<n;i++){
if(k[i]<k[i-1]){
temp=k[i];
for(j=i-1;k[j]>temp;j--){
k[j+1]=k[j];
}
k[j+1]=temp;
}
}
}
void InsertSort(int k[],int low,int high){
ISort(k+low,high-low+1);
}
int Partition(int k[],int low,int high){
int point;
int m=low+(high-low)/2;
if(k[low]>k[high]){
swap(k,low,high);
}
if(k[m]>k[high]){
swap(k,m,high);
}
if(k[m]>k[low]){
swap(k,m,low);
}
point=k[low];
while(low<high){
while(low<high && k[high]>=point){
high--;
}
k[low]=k[high];
while(low<high && k[low]<=point){
low++;
}
k[high]=k[low];
}
k[low]=point;
return low;
}
void Qsort(int k[],int low,int high){
int point;
if( (high-low) > MAX_LENGTH_INSERT_SORT ){
while(low<high){
point=Partition(k,low,high);
if(point-low<high-point){
Qsort(k,low,point-1);
low=point+1;
}else{
Qsort(k,point+1,high);
high=point-1;
}
}
}else{
InsertSort(k,low,high);
}
}
void QuickSort(int k[], int n)
{
Qsort(k,0,n-1);
}
int main()
{
int i, a[10] = {5, 2, 6, 0, 3, 9, 1, 7, 4, 8};
QuickSort(a, 10);
printf("排序后的结果是:\n");
for( i=0; i < 10; i++ )
{
printf("%d ", a[i]);
}
printf("\n\n");
return 0;
}
排序效果如下图所示:
2、我们用java语言将其实现,代码如下:
package 数据结构.排序;
public class 快速排序 {
public static void quickSort(int[] arr,int low,int high){
int i,j,temp,t;
if(low>high){
return;
}
i=low;
j=high;
//temp就是基准位
temp = arr[low];
while (i<j) {
//先看右边,依次往左递减
while (temp<=arr[j]&&i<j) {
j--;
}
//再看左边,依次往右递增
while (temp>=arr[i]&&i<j) {
i++;
}
//如果满足条件则交换
if (i<j) {
t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
//最后将基准为与i和j相等位置的数字交换
arr[low] = arr[i];
arr[i] = temp;
//递归调用左半数组
quickSort(arr, low, j-1);
//递归调用右半数组
quickSort(arr, j+1, high);
}
public static void main(String[] args){
int[] arr = {10,7,2,4,7,62,3,4,2,1,8,9,19};
quickSort(arr, 0, arr.length-1);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
排序效果如下图所示:
3、我们用python语言将其实现,代码如下:
def quick_sort(alist, first, last):
if first >=last:
return
mid_value = alist[first]
low = first
high = last
while low < high:
while low < high and alist[high] >= mid_value:
high -= 1
alist[low] = alist[high]
while low < high and alist[low] < mid_value:
low+=1
alist[high] = alist[low]
alist[low] = mid_value
quick_sort(alist,first, low-1)
quick_sort(alist, low+1, last)
if __name__ == '__main__':
li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
print("排序之前为:", li)
quick_sort(li,0,len(li) - 1)
print("排序之后为:",li)
排序效果如下图所示:
7、堆排序
堆排序的原理在于将待排序序列构造成一个大顶堆(arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2] ),此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。我们用动图解释如下:
接下来我们分别用C语言、java和python3三种语言将其实现:
1、我们用C语言将其实现,代码如下:
#include <stdio.h>
void swap(int k[],int i,int j ){
int temp;
temp=k[i];
k[i]=k[j];
k[j]=temp;
}
void HeapAdjust(int k[],int s,int n){
int i,temp;
temp=k[s];
for(i=2*s;i<=n;i*=2){
if(i<n && k[i]<k[i+1]){
i++;
}
if(temp>=k[i]){
break;
}
k[s]=k[i];
s=i;
}
k[s]=temp;
}
void HeapSort(int k[],int n){
int i;
for(i=n/2;i>0;i--){
HeapAdjust(k,i,n);
}
for(i=n;i>1;i--){
swap(k,1,i);
HeapAdjust(k,1,i-1);
}
}
int main(){
int i,a[10]={-1,5,2,6,0,3,9,1,7,4};
HeapSort(a,9);
printf("排序后的结果是: \n");
for(i=1;i<10;i++){
printf("%d ",a[i]);
}
printf("\n\n");
return 0;
}
排序效果如下图所示:
2、我们用java语言将其实现,代码如下:
package 数据结构.排序;
public class 堆排序 {
/**
* 堆排序演示
*
* @author Napoleon
*/
public static void main(String[] args) {
int[] arr = {16, 7, 3, 20, 17, 8};
heapSort(arr);
for (int i : arr) {
System.out.print(i + " ");
}
}
/**
* 创建堆,
* @param arr 待排序列
*/
private static void heapSort(int[] arr) {
//创建堆
for (int i = (arr.length - 1) / 2; i >= 0; i--) {
//从第一个非叶子结点从下至上,从右至左调整结构
adjustHeap(arr, i, arr.length);
}
//调整堆结构+交换堆顶元素与末尾元素
for (int i = arr.length - 1; i > 0; i--) {
//将堆顶元素与末尾元素进行交换
int temp = arr[i];
arr[i] = arr[0];
arr[0] = temp;
//重新对堆进行调整
adjustHeap(arr, 0, i);
}
}
/**
* 调整堆
* @param arr 待排序列
* @param parent 父节点
* @param length 待排序列尾元素索引
*/
private static void adjustHeap(int[] arr, int parent, int length) {
//将temp作为父节点
int temp = arr[parent];
//左孩子
int lChild = 2 * parent + 1;
while (lChild < length) {
//右孩子
int rChild = lChild + 1;
// 如果有右孩子结点,并且右孩子结点的值大于左孩子结点,则选取右孩子结点
if (rChild < length && arr[lChild] < arr[rChild]) {
lChild++;
}
// 如果父结点的值已经大于孩子结点的值,则直接结束
if (temp >= arr[lChild]) {
break;
}
// 把孩子结点的值赋给父结点
arr[parent] = arr[lChild];
//选取孩子结点的左孩子结点,继续向下筛选
parent = lChild;
lChild = 2 * lChild + 1;
}
arr[parent] = temp;
}
}
排序效果如下图所示:
3、我们用python语言将其实现,代码如下:
import random
def MAX_Heapify(heap,HeapSize,root):#在堆中做结构调整使得父节点的值大于子节点
left = 2*root + 1
right = left + 1
larger = root
if left < HeapSize and heap[larger] < heap[left]:
larger = left
if right < HeapSize and heap[larger] < heap[right]:
larger = right
if larger != root:#如果做了堆调整则larger的值等于左节点或者右节点的,这个时候做对调值操作
heap[larger],heap[root] = heap[root],heap[larger]
MAX_Heapify(heap, HeapSize, larger)
def Build_MAX_Heap(heap):#构造一个堆,将堆中所有数据重新排序
HeapSize = len(heap)#将堆的长度当独拿出来方便
for i in range((HeapSize -2)//2,-1,-1):#从后往前出数
MAX_Heapify(heap,HeapSize,i)
def HeapSort(heap):#将根节点取出与最后一位做对调,对前面len-1个节点继续进行对调整过程。
Build_MAX_Heap(heap)
for i in range(len(heap)-1,-1,-1):
heap[0],heap[i] = heap[i],heap[0]
MAX_Heapify(heap, i, 0)
return heap
if __name__ == '__main__':
a = [30,50,57,77,62,78,94,80,84]
print (a)
HeapSort(a)
print (a)
b = [random.randint(1,1000) for i in range(1000)]
print (b)
HeapSort(b)
print (b)
排序效果如下图所示:
8、计数排序
计数排序的原理在于利用桶的概念:一个桶存单一的键值。根据待排序数组A创建计数数组B,B首位代表A最小值,B末位代表A最大值,遍历A,在B中记录所有元素出现次数。遍历B,根据索引与最小值距离,以及出现次数排序。我们用动图解释如下:
接下来我们分别用C语言、java和python3三种语言将其实现:
1、我们用C语言将其实现,代码如下:
#include <stdio.h>
#include <stdlib.h>
#define random(x) rand()%(x)
#define NUM 100 // 产生100个随机数
#define MAXNUM 200 //待排序的数字范围是0-200
void countingSort(int A[], int n, int k){
int *c, *b;
int i;
c = (int *)malloc(sizeof(int)*k);/*临时数组,注意它的大小是待排序序列中值最大的那个。如假定该排序序列中最大值为1000000,则该数组需要1000000*sizeof(int)个存储单元*/
b = (int *)malloc(sizeof(int)*n); /*存放排序结果的数组*/
for (i = 0; i < k; i++)
c[i] = 0; /*初始化*/
for (i = 0; i < n; i++)
c[A[i]] += 1; /*统计数组A中每个值为i的元素出现的次数*/
for (i = 1; i < k; i++)
c[i] = c[i - 1] + c[i]; /*确定值为i的元素在数组c中出现的位置*/
for (i = n - 1; i >= 0; i--)
{
b[c[A[i]] - 1] = A[i]; /*对A数组,从后向前确定每个元素所在的最终位置;*/
c[A[i]] -= 1;
}
for (i = 0; i < n; i++)
A[i] = b[i]; /*这个目的是返回A数组作为有序序列*/
free(c);
free(b);
}
void printArray(int A[], int n){
int i = 0;
for (i = 0; i < n; i++){
printf("%4d", A[i]);
}
printf("\n");
}
/*测试*/
int main()
{
int A[NUM];
int i;
for (i = 0; i < NUM; i++)
A[i] = random(MAXNUM);
printf("before sorting:\n");
printArray(A, NUM);
countingSort(A, NUM, MAXNUM);
printf("after sorting:\n");
printArray(A, NUM);
return 0;
}
排序效果如下图所示:
2、我们用java语言将其实现,代码如下:
package 数据结构.排序;
public class 计数排序 {
private static int[] countSort(int[] array,int k)
{
int[] C=new int[k+1];//构造C数组
int length=array.length,sum=0;//获取A数组大小用于构造B数组
int[] B=new int[length];//构造B数组
for(int i=0;i<length;i++)
{
C[array[i]]+=1;// 统计A中各元素个数,存入C数组
}
for(int i=0;i<k+1;i++)//修改C数组
{
sum+=C[i];
C[i]=sum;
}
for(int i=length-1;i>=0;i--)//遍历A数组,构造B数组
{
B[C[array[i]]-1]=array[i];//将A中该元素放到排序后数组B中指定的位置
C[array[i]]--;//将C中该元素-1,方便存放下一个同样大小的元素
}
return B;//将排序好的数组返回,完成排序
}
public static void main(String[] args)
{
int[] A=new int[]{2,5,3,0,2,3,0,3};
int[] B=countSort(A, 5);
for(int i=0;i<A.length;i++)
{
System.out.println((i+1)+"th:"+B[i]);
}
}
}
排序效果如下图所示:
3、我们用python语言将其实现,代码如下:
def countingSort(arr, maxValue):
bucketLen = maxValue+1
bucket = [0]*bucketLen
for a in arr:
if not bucket[a]:
bucket[a]=0
bucket[a]+=1
arr.clear()
for i in range(len(bucket)):
while bucket[i]>0:
arr.append(i)
bucket[i]-=1
return arr
if __name__ == "__main__":
int_list = [1,8,6,5,6,6,8,4,8]
c = countingSort(int_list,10)
print(c)
排序效果如下图所示:
9、桶排序
桶排序的原理利用桶的概念:一个桶存一定范围的元素。设置一个定量的数组当作空桶,遍历输入数据,并且把数据一个一个放到对应的桶里去,对每个不是空的桶进行排序,从不是空的桶里把排好序的数据拼接起来。我们用动图解释如下:
接下来我们分别用C语言、java和python3三种语言将其实现:
1、我们用C语言将其实现,代码如下:
#include <stdio.h>
#include <string.h>
int a[101],n;
/* 桶排序之随机输入n个数,最后从小到大排列输出 */
void BucketSort(void)
{
int a[101];
int i;
int j;
int temp;
for(i=0;i<=100;i++)
a[i]=0;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&temp);
a[temp]++;
}
for(i=0;i<=100;i++)
for(j=1;j<=a[i];j++)
{
printf("%d ",i);
}
printf("\n");
}
int main(int argc,char**argv)
{
BucketSort(); /* 桶排序调用 */
return 0;
}
排序效果如下图所示:
2、我们用java语言将其实现,代码如下:
package 数据结构.排序;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
public class 桶排序 {
public static void main(String[] args) {
// 输入元素均在 [0, 10) 这个区间内
float[] arr = new float[] { 0.12f, 2.2f, 8.8f, 7.6f, 7.2f, 6.3f, 9.0f, 1.6f, 5.6f, 2.4f };
bucketSort(arr);
printArray(arr);
}
public static void bucketSort(float[] arr) {
// 新建一个桶的集合
ArrayList<LinkedList<Float>> buckets = new ArrayList<LinkedList<Float>>();
for (int i = 0; i < 10; i++) {
// 新建一个桶,并将其添加到桶的集合中去。
// 由于桶内元素会频繁的插入,所以选择 LinkedList 作为桶的数据结构
buckets.add(new LinkedList<Float>());
}
// 将输入数据全部放入桶中并完成排序
for (float data : arr) {
int index = getBucketIndex(data);
insertSort(buckets.get(index), data);
}
// 将桶中元素全部取出来并放入 arr 中输出
int index = 0;
for (LinkedList<Float> bucket : buckets) {
for (Float data : bucket) {
arr[index++] = data;
}
}
}
/**
* 计算得到输入元素应该放到哪个桶内
*/
public static int getBucketIndex(float data) {
// 这里例子写的比较简单,仅使用浮点数的整数部分作为其桶的索引值
// 实际开发中需要根据场景具体设计
return (int) data;
}
/**
* 我们选择插入排序作为桶内元素排序的方法 每当有一个新元素到来时,我们都调用该方法将其插入到恰当的位置
*/
public static void insertSort(List<Float> bucket, float data) {
ListIterator<Float> it = bucket.listIterator();
boolean insertFlag = true;
while (it.hasNext()) {
if (data <= it.next()) {
it.previous(); // 把迭代器的位置偏移回上一个位置
it.add(data); // 把数据插入到迭代器的当前位置
insertFlag = false;
break;
}
}
if (insertFlag) {
bucket.add(data); // 否则把数据插入到链表末端
}
}
public static void printArray(float[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + ", ");
}
System.out.println();
}
}
排序效果如下图所示:
3、我们用python语言将其实现,代码如下:
def bucketSort(nums):
# 选择一个最大的数
max_num = max(nums)
# 创建一个元素全是0的列表, 当做桶
bucket = [0]*(max_num+1)
# 把所有元素放入桶中, 即把对应元素个数加一
for i in nums:
bucket[i] += 1
# 存储排序好的元素
sort_nums = []
# 取出桶中的元素
#bucket = [0, 2, 2, 2, 1, 0, 2, 1, 1, 2]
for j in range(len(bucket)):
#桶上为0就没必要统计了,不为0的时候继续
if bucket[j] != 0:
#确定打印次数,当j = 1 bucket[j] = 2,下面的for y 就会打印两次
for y in range(bucket[j]):
#j = 1 打印两次也就是总共出现了两次
sort_nums.append(j)
return sort_nums
num = [6, 3, 9, 2, 1, 4, 3, 2, 1, 9, 8, 7, 6]
print(bucketSort(num))
排序效果如下图所示:
10、基数排序
基数排序的原理在于利用桶的概念:根据键值的每位数字来分配桶。取得数组中的最大数,并取得位数;arr为原始数组,从最低位开始取每个位组成radix数组;对radix进行计数排序(利用计数排序适用于小范围数的特点);我们用动图解释如下:
接下来我们分别用C语言、java和python3三种语言将其实现:
1、我们用C语言将其实现,代码如下:
#include<stdio.h>
#include<math.h>
#define SIZE 10
#define C_SIZE 20
/*行数稳定=10,切记!列数务必搞的大一些,避免出现放不下的情况*/
void radix_sort(int *p,int n,int step);
void travel(int *p,int n);
int get_max(int *p,int n);
int get_digit(int x);
void out_sort(int *p,int n);
main() {
int a[]= {8,7,83,4,39,85,5,9,32,80,97,61,2,38,48,74,26,514};
int *pa=a;
int i;
int n=sizeof(a)/sizeof(int);
travel(pa,n);
out_sort(pa,n);
travel(pa,n);
}
void out_sort(int *p,int n) {
int max_n=get_max(p,n);
int loop=get_digit(max_n);
int i;
for(i=1; i<=loop; i++) {
radix_sort(p,n,i);
}
}
/*基数排序*/
void radix_sort(int *p,int n,int loop) {
int b[SIZE][C_SIZE]= {0};
int i,j;
int tmp =(int)pow(10,loop-1);
for(i=0; i<n; i++) {
int row_index = (*(p+i)/tmp)%10;
for(j=0; j<C_SIZE; j++) {
if(b[row_index][j] == 0) {
b[row_index][j] = *(p+i);
break;
}
}
}
int k=0;
for(i=0; i<SIZE; i++) {
for(j=0; j<C_SIZE; j++) {
if(b[i][j]!= 0) {
*(p+k)=b[i][j];
b[i][j]=0;
k++;
}
}
}
}
/*遍历数组*/
void travel(int *p,int n) {
int i;
for(i=0; i<n; i++) {
printf("%d,",*(p+i));
}
printf("\n");
}
/*获取最大数字*/
int get_max(int *p,int n) {
int i;
int max_num=0;
for(i=0; i<n; i++) {
if(*(p+i)>max_num) {
max_num=*(p+i);
}
}
return max_num;
}
/*获取最大数字的位数*/
int get_digit(int x) {
int count=1;
int temp=x/10;
while(temp!=0) {
count++;
temp=temp/10;
}
return count;
}
排序效果如下图所示:
2、我们用java语言将其实现,代码如下:
package 数据结构.排序;
import java.util.*;
public class 基数排序 {
public static void main(String[] args) {
int[] arr = {63, 157, 189, 51, 101, 47, 141, 121, 157, 156,
194, 117, 98, 139, 67, 133, 181, 12, 28, 0, 109};
radixSort(arr);
System.out.println(Arrays.toString(arr));
}
/**
* 高位优先法
*
* @param arr 待排序列,必须为自然数
*/
private static void radixSort(int[] arr) {
//待排序列最大值
int max = arr[0];
int exp;//指数
//计算最大值
for (int anArr : arr) {
if (anArr > max) {
max = anArr;
}
}
//从个位开始,对数组进行排序
for (exp = 1; max / exp > 0; exp *= 10) {
//存储待排元素的临时数组
int[] temp = new int[arr.length];
//分桶个数
int[] buckets = new int[10];
//将数据出现的次数存储在buckets中
for (int value : arr) {
//(value / exp) % 10 :value的最底位(个位)
buckets[(value / exp) % 10]++;
}
//更改buckets[i],
for (int i = 1; i < 10; i++) {
buckets[i] += buckets[i - 1];
}
//将数据存储到临时数组temp中
for (int i = arr.length - 1; i >= 0; i--) {
temp[buckets[(arr[i] / exp) % 10] - 1] = arr[i];
buckets[(arr[i] / exp) % 10]--;
}
//将有序元素temp赋给arr
System.arraycopy(temp, 0, arr, 0, arr.length);
}
}
}
排序效果如下图所示:
3、我们用python语言将其实现,代码如下:
def RadicSort(arr):
MinnumLength = 1
MaxNumLength = len(str(max(arr)))
while MinnumLength<=MaxNumLength:
bucket_list =[[] for _ in range(10)]
for a in arr:
Digits = int(a / (10**MinnumLength)) % 10
bucket_list[Digits].append(a) # 找到位置放入桶数组
arr.clear()
for bucket in bucket_list:
for num in bucket:
arr.append(num)
MinnumLength+=1
return arr
if __name__ == "__main__":
int_list = [4, 56, 87, 56, 23, 78, 69, 23, 321,66]
r = RadicSort(int_list)
print(r)
排序效果如下图所示:
以上就是这十大核心排序算法的原理,为了方便大家理解,每种算法都配有动态图帮助大家更好的理解。每种算法都给出了当前最主流的三种语言(C、java、python),希望大家至少要熟练掌握一种语言,不仅仅要在编译器上写出来,也得需要在A4白纸上写出正确的代码,这是作为一个秋招的学生应具备的基本的专业素养。最后,在给大家分析每种算法的时间和空间复杂度以及相应的排序过程的稳定性。
十大排序算法复杂度分析
我们可以通过一张表来概括其各大算法的时间复杂度以及相应的空间复杂度和在排序过程中是否稳定。在介绍之前,我们首先给大家普及时间复杂度、空间复杂度和稳定性的相关知识。通常,对于一个给定的算法,我们要做 两项分析。第一是从数学上证明算法的正确性,这一步主要用到形式化证明的方法及相关推理模式,如循环不变式、数学归纳法等。而在证明算法是正确的基础上,第二部就是分析算法的时间复杂度。算法的时间复杂度反映了程序执行时间随输入规模增长而增长的量级,在很大程度上能很好反映出算法的优劣与否。因此,作为程序员,掌握基本的算法时间复杂度分析方法是很有必要的。
算法执行时间需通过依据该算法编制的程序在计算机上运行时所消耗的时间来度量。一个算法的空间复杂度(Space Complexity)S(n)定义为该算法所耗费的存储空间,它也是问题规模n的函数。渐近空间复杂度也常常简称为空间复杂度。
空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度。一个算法在计算机存储器上所占用的存储空间,包括存储算法本身所占用的存储空间,算法的输入输出数据所占用的存储空间和算法在运行过程中临时占用的存储空间这三个方面。算法的输入输出数据所占用的存储空间是由要解决的问题决定的,是通过参数表由调用函数传递而来的,它不随本算法的不同而改变。存储算法本身所占用的存储空间与算法书写的长短成正比,要压缩这方面的存储空间,就必须编写出较短的算法。算法在运行过程中临时占用的存储空间随算法的不同而异,有的算法只需要占用少量的临时工作单元,而且不随问题规模的大小而改变,我们称这种算法是“就地"进行的,是节省存储的算法,如这一节介绍过的几个算法都是如此;有的算法需要占用的临时工作单元数与解决问题的规模n有关,它随着n的增大而增大,当n较大时,将占用较多的存储单元。
排序算法的稳定性通俗地讲就是能保证排序前两个相等的数据其在序列中的先后位置顺序与排序后它们两个先后位置顺序相同。即:如,如果A i == A j,Ai 原来在 Aj 位置前,排序后 Ai 仍然是在 Aj 位置前。如果排序算法是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所利用。基数排序就是这样,先按低位排序,逐次按高位排序,那么,低位相同的数据元素其先后位置顺序即使在高位也相同时是不会改变的。学习排序原理时,可能编的程序里面要排序的元素都是简单类型,实际上真正应用时,可能是对一个复杂类型(自定义类型)的数组排序,而排序的键值仅仅只是这个元素中的一个属性,对于一个简单类型,数字值就是其全部意义,即使交换了也看不出什么不同。但是,对于复杂类型,交换的话可能就会使原本不应该交换的元素交换了。比如:一个“学生”数组,欲按照年龄排序,“学生”这个对象不仅含有“年龄”,还有其它很多属性。假使原数组是把学号作为主键由小到大进行的数据整理。而稳定的排序会保证比较时,如果两个学生年龄相同,一定不会交换。也就意味着尽管是对“年龄”进行了排序,但是学号顺序仍然是由小到大的要求。如果排序算法稳定,对基于比较的排序算法而言,元素交换的次数可能相对会少一些。
接下来,我们用一张表来总结这十大算法的相应的复杂度和是否稳定。
总结
本文介绍了十大排序算法的原理、用动态图描述算法的过程。分别用C语言程序设计、java程序设计以及python实现。最后给大家介绍了算法的空间、时间复杂度以及在排序过程中是否稳定,通过一张图来总结了十大算法所占的空间和时间复杂度以及相应的稳定性。这是我们学其他计算机相关专业的大前提。数据结构理解的好坏直接决定我们代码的优劣程度,因此需要我们对数据结构与算法有着深层次的理解。下一篇文章将会给大家介绍数据结构中最为重要的查找算法。最后希望大家学有所成,未来可期,同时也希望自己能够早日找到心仪的工作!!!!!