内部排序算法

算法

排序

影响程序效率的最大因素之一为: IO ------》输入和输出(读写文件)

  • 内部排序

    数据存储在内存中

    插入排序

    • 直接插入排序O(n2)

      假设前方序列已经有序,将后方元素逐一插入前方,插入之后保证有序

      循环从第一个元素开始,逐一向后

    7 2  5 3 0 9 8 
    
    第一轮:(7)2 5 3 0 9 8  区间[0,0]有序
    第二轮:(2 7)5 3 0 9 8  区间[0,1]有序
    第三轮:(2 5 7)3 0 9 8  区间[0,2]有序
    第四轮:(2 3 5 7)0 9 8  区间[0,3]有序
    第五轮:(0 2 3 5 7)9 8  区间[0,4]有序
    第六轮:(0 2 3 5 7 9)8  区间[0,5]有序
    第七轮:(0 2 3 5 7 8 9) 区间[0,6]有序
    完成!
    

    代码实现如下:

    #define SWAP(a,b) {(a) = (a) + (b); \
    				(b) = (a) - (b); \
    				(a) = (a) - (b);}
    
    void Direct_Insertion_Sort(int *arr)
    {
    	for(int i = 1; i < sizeof(arr)/sizeof(arr[0]); i++){
    		for(int j = 0; j < i; j++){
    			if(arr[j] < arr[i]){
    				SWAP(arr[i], arr[j]);
                    break;
                }
            }
        }
    }
    //或者换一种写法
    void DIS(int *arr, size_t n)
    {
    	int i, j;
        //由于第一个元素一定是有序的,因此不需要进行插入
        for(i = 1; i < n; i++){
            int key = arr[i];//记录需要插入的元素为key
    		for(j = i-1; j >= 0 && arr[j] < arr[i]; --j){//
    			arr[j+1] = arr[j];
            }
            if(j+1 != i){
    			arr[j+1] = key;
            }
        }
    }
    
    
    
    • 二分插入排序 O(n2)

      使用折半的思想,快速找到位置,将后方元素逐一后移

    7 2  5 3 0 9 8 
    
    第一轮:(7)2 5 3 0 9 8  区间[0,0]有序
    第二轮:(2 7)5 3 0 9 8  区间[0,1]有序
    第三轮:(2 5 7)3 0 9 8  区间[0,2]有序
    第四轮:(2 3 5 7)0 9 8  区间[0,3]有序
    第五轮:(0 2 3 5 7)9 8  区间[0,4]有序
    第六轮:(0 2 3 5 7 9)8  区间[0,5]有序
    第七轮:(0 2 3 5 7 8 9) 区间[0,6]有序
    完成!
    

    代码实现:

    void Binary_Insertion_Sort(int *arr, size_t n)
    {
    	int i, j;
        for(i = 1; i < n; i++){
            int left = 0, right = i - 1;
            int key = arr[i];
            while(left <= right){
                int mid = (left + right)/2;
    			if(arr[mid] <= key){
    				left = mid + 1;
                }
                else{
    				right = mid - 1;
                }
            } 
    		for(j = i-1; j > right; --j){//
    			arr[j+1] = arr[j];
            }
            arr[right + 1] = key;
        }
    }//数据量大了会令效果明显;
    
    • 二路插入排序 空间复杂度O(n)
    7 2  5 3 0 9 8
     7                2
    max              min
    5 7                2
     max              min
    3 5 7                2
       max              min
    3 5 7               0 2
       max             min
    3 5 7 9               0 2
         max             min   
    3 5 7 8 9               0 2
           max             min     
    
    void Two_Way_Insertion_Sort(int *arr, size_t n)
    {
    	int brr[n];
        //需要把arr中元素二分插入到brr
        int max = 0, min = 0;
        brr[0] = arr[0];
        for(int i = 1; i < n; i++){
    		if(arr[i] > brr[max]){
    			brr[++max] = arr[i];
            }
            else if(arr[i] < brr[min]){
    			min = (min - 1 + (int)n)%n;
                brr[min] = arr[i];
            }
            else if(arr[i] > brr[0]){
                for(int j = max++; j >= 0 && brr[j] > arr[i]; --j){
    				brr[j+1] = brr[j];
                }
                brr[j+1] = arr[i];
            }
            else {
    			for(int j = min++; j < n && brr[j] < arr[i]; ++j){
    				brr[j-1] = brr[j];
                }
                brr[j-1] = arr[i];
            }
        }
        for(int i = 0; i < n; i++){
    		arr[i] = brr[(i+min)%n]
        }
    }
    
    • 希尔排序 O(nlog2n)

      7 2  5 3 0 9 8
      分组下标组合
      
      
      

      代码实现如下:

    void Shell_Sort(int *arr)
    {
    	int step, i, j;
        for(step = n/2; step > 0; step /= 2){
    		for(i = step; i < n; i++){
               int key = arr[i];
                for(j = i - step; j > 0 && key < arr[j]; j -= step){
    				arr[j + step] = arr[j];
                }
                if(j+step != i)
                    arr[j+step] = key;
            }
        }
    }
    

    选择排序

    ​ 每次选择一个最大值或最小值

    • 简单选择排序

      每次遍历剩下的元素从中选取最大的或最小的元素和最后一个元素交换位置,循环n-1次,区间变小

      时间复杂度为O(n2)

    7 2 5 3 0 9 8
    
    7 2 5 3 0 8 9
    
    7 2 5 3 0 8 9
    
    0 2 5 3 7 8 9 
    
    0 2 5 3 7 8 9
    
    0 2 3 5 7 8 9
    
    void Simple_Select_Sort(int *arr, size_t n)
    {
    	int i, j;
        for(i = 1; i < n; i++){
            //记录最大值的下标
            int index = 0;
    		for(j = 0; j < n-i; j++){
    			if(arr[index] = arr[j]){
    				index = j;
                }
            }
            if(index !=  n-i){
    			SWAP(arr[index], arr[n-i]);
            }
        }
    }
    
    • 堆排序

      一个结点的下标为i则其父结点的下标为i/2, 其子结点为2i ,2i+1

    void reheap(int *arr, int pos, size_t n)
    {
    	int key = arr[pos];
        int child = 2*pos + 1;
        while(child < n){
    		if(child+1 < n && arr[child] < arr[child+1]){
    			++child;
            }
            if(key < arr[child]){
    			arr[pos] = arr[child];
                pos = child;
                child = 2*pos + 1;
            }
            else{
    			break;
            }
        }
        arr[pos] = key;
    }
    
    void Heap_Sort(int *arr, size_t n)
    {
    	int i, j;
        for(i = n/2; i >= 0; i--){
    		reheap(arr, i, n);
        }
        for(i = (int)n-1; i > 0; --i){
    		SWAP(arr[0], arr[i]);
            reheap(arr, 0 ,i);
        }
    }
    
    • 冒泡排序

      相邻的两个元素进行比较

    //老师说这是他觉得最没意思的排序
    void Bubble_Sort(int *arr, size_tn)
    {
    	int i, j, flag = 0;;
        for(i = 0; i < n-1; i++){
            flag = 0;
    		for(j = 1; j < n-i; j++){
    			if(arr[j] < arr[j-1]){
                    flag = 1;
                    SWAP(arr[j], arr[j-1]);
                }
            }
            if(flag == 0){
    			break;
            }
        }
    }
    
    • 鸡尾酒排序

      同时选取最大值和最小值与区间开头和结尾的元素交换,逐渐做小区间即可得到有序数组

    void Cocktail_Ordering(int *arr, size_t n)
    {
    	int i, j;
        for(i = 0; i < n/2; ++i){
    		int min = i, max = i;
            for(j = i+1; j< n-i; ++j){
    			if(arr[j] < arr[min]){
                	min = j
                }
                else if(arr[max] < arr[j]){
    				max = j;
                }
            }
            if(min != i)
                SWAP(arr[min], arr[i]);
            if(max == i)
                max = min;
            if(max != n-i-1)
                SWAP(arr[max], arr[n-i-1]);
        }
    }
    

    快速排序

    核心思想:对于任何一个元素key都能找到一个位置,使其左边元素全部小于等于key

    7 2 5 3 0 9 8
    
    
    //对[left,right]区间进行排序 
    void quick_part_sort(int arr[],int left,int right){
    	if(left>=right)
    		return;
    	int key = arr[left];
    	int i=left,j=right;
    	while(i<j){
    		while(i<j && key<=arr[j]) --j;
    		arr[i] = arr[j];   //if(i!=j)
    		while(i<j && arr[i]<=key) ++i;
    		arr[j] = arr[i];   //if(i!=j)
    	} 
    	arr[i] = key;
    	if(i-1>left)
    		quick_part_sort(arr,left,i-1);
    	if(right>i+1)
    		quick_part_sort(arr,i+1,right); 
    }
    
    
    void quick_sort(int arr[],size_t n){
    	//quick_part_sort(arr,0,n-1);	
    	if(n<=1)
    		return;
    	
    	int key = arr[0];
    	int left=0,right=n-1;
    	while(left < right){//left == right结束循环 
    		while(left<right && key <= arr[right]){
    			--right;
    		} 
    		arr[left] = arr[right];
    		while(left<right && arr[left] <= key){
    			++left;
    		}
    		arr[right] = arr[left];
    	}
    	arr[left] = key;
    	//[0,left-1]
    	if(left>1){//左边有2个或者以上的元素   left
    		quick_sort(arr,left); 
    	}
    	//[left+1,n-1]    //n-1>left+1
    	if(n>left+1){
    		quick_sort(arr+left+1,n-left-1); 
    	}	
    }
    

    归并排序(两路归并)

    void merge_arr(int arr[],size_t n){
    	int mid = n/2;
    	//[0,mid) 区间升序    [mid,n) 区间升序	  进行合并 使得[0,n)区间都升序 
    	int brr[mid];//用于存储[0,mid)部分数据
    	int i,j,k;
    	for(i=0;i<mid;++i){
    		brr[i] = arr[i];
    	} 
    	//i用于记录放回去元素的下标位置  j记录brr的下标位置  k记录的是arr[mid-n)部分下标 
    	i=0,j=0,k=mid;
    	while(j<mid && k<n){//brr和arr[mid-n)两部分数据都还有
    		if(brr[j] < arr[k]){
    			arr[i++] = brr[j++];
    		} else {
    			arr[i++] = arr[k++];
    		}	
    	} 	
    	while(j<mid){
    		arr[i++] = brr[j++];
    	}	
    }
    
    void merge_sort(int arr[],size_t n){
    	if(n<=1)
    		return;
    	
    	int mid = n/2;  //[0,mid)   [mid,n)       n==2   [0,1) [1,2)
    	merge_sort(arr,mid);  //[0,mid) 区间中的元素排序 
    	merge_sort(arr+mid,n-mid);  //[mid,n) 区间中的元素排序 
    	merge_arr(arr,n); 
    }
    

    基数排序

    思路:遍历所有元素找到最大值,根据其位数依次对个位、十位上的数字放入对应的坑内

    用数组存储难以确定每个坑的大小,用单链表难以做到插入末尾,因此这里使用双向循环链表

    132 456 34 673 8 99 105 126 577 12 46
    个位
    0    1    2    3    4   5    6    7    8    9    
             132  673  34   105 456   577  8    99
              12                126     
                                 46
    十位                             
    0    1    2    3    4   5    6    7    8    9    
    105  12  126  132   46 456       673       99
    8             34                 577
    百位
    0    1    2    3    4   5    6    7    8    9    
    8   105            456 577  673
    12  126
    34  132
    46
    99
    
    排序前:132 456 34 673 8 99 105 126 577 12 46
    
    排序后:8 12 34 46 99 105 126 132 456 577 673
    
    
    
    void Radix_Sort(int *arr, size_t n)
    {
    	DLinkedList bucket[10];
    	int i,j;
    	for(i=0;i<10;++i){
    		bucket[i] = create_dlinkedlist();为每个坑创建头结点
    	}
    	size_t max = 0;
    	for(i=0;i<n;++i){
    		if(max<arr[i]){
    			max = arr[i];
    		}
    	}
    	int div=1;
    	for(;max!=0;max/=10,div*=10){
    		for(i=0;i<n;++i){
    			int b = arr[i]/div%10;//取个位  十位  百位  千位
    			push_back_dlinkedlist(bucket[b],&arr[i],sizeof(arr[i]));
    		}
    		for(i=0,j=0;i<10;++i){
    			while(!empty_dlinkedlist(bucket[i])){
    				//pop_front_dlinkedlist(bucket[i],&arr[j],sizeof(arr[j]));
    				arr[j] = *(size_t *)front_dlinkedlist(bucket[i]);
    				pop_front_dlinkedlist(bucket[i]);
    				++j;
    			}
    		}
    	}
    	
    	for(i=0;i<10;++i){
    		destroy_dlinkedlist(bucket[i]);
    	}
    }
    

    桶子排序

    • 把不同的数据放到不同的桶内,依次对桶子内的元素排序,然后顺序回收

    计数排序

    • 有100个100以内的整数,问用哪种块
    • 在已知范围的情况下,建立范围内的每一个整数的数组,遍历后将遍历到元素的对应的位置数组值加1,遍历完成后,依次输出。


  • 外部排序

    数据存储在文件中,一次性无法将数据全部载入内存中进行操作,只能分批次加载,操作完成后写回程序

多路归并排序

  • 二路归并

  • 初始归并段

    • 加载到内存中,用内部排序,排序后写回
    • 选择置换序列来初始化初始归并段
  • 败者树

  • 最佳归并树

排序的一些性质

  • 稳定性:

    如果原始序列中有相同的元素所在的位置为(i, j)经过排序之后,这两个元素的位置为i’, j’ 且i’ < j’,则称该排序为稳定排序

    排序方法平均时间复杂度最坏情况空间复杂度稳定性
    直接插入O(n2)O(n2)O(1)稳定
    二分插入O(n)O(n2)O(1)不稳定
    二路插入O(n2)O(n2)O(n)稳定
    希尔O(nlog2n)/O(n1.3)O(nlog2n)O(1)不稳定
    简单选择/冒泡O(n2)O(n2)O(1)稳定
    快速O(nlog2n)O(n2)O(log2n)最坏为O(n)不稳定
    堆排序O(nlog2n)O(nlog2n)O(1)不稳定
    归并排序O(nlog2n)O(nlog2n)O(n)稳定
    基数排序稳定

    空间复杂度:申请额外动态内存(基于某个变量),或者定义非定长数组 或者递归调用(计算递归的深度)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值