排序算法的 java实现(二)

⑤ 快速排序

实现快速排序需要在每一趟排序中找一个枢纽量pivotKey,并设置变量pivotIndex用来记录pivotKey应该放在哪个位置

假定枢纽量为排序区间的最左元素(pivotKey = arr[ left ]),

在每一趟排序中,设置 两个标兵left与right,用right 从右往左小于pivotKey的元素,找到后停下来,将该元素复制到left所在的位置;此时用left 从左到右大于pivotKey的元素 ,找到后停下来,将该元素复制到right所在的位置,以此循环。

当left与right相遇时,就将pivotKey插入到两个指针相遇的位置,该趟排序结束。

下一趟排序则以pivotKey所在的位置 为分界线,对两个区间进行递归快速排序操作

实现一趟快速排序的代码为:

//快速排序每一趟排序的算法
public static int divideArr(int[] arr , int left , int right){
    int pivotKey ; 
    pivotKey = arr[left];   //假定每趟排序的枢纽量为该区间的最左端元素,先用pivotKey将它保存起来
    
    while( left < right ){
        while( left < right && arr[right] >= pivotKey)
            right -- ;
        while( left < right && arr[left] <= pivotKey)
            left ++ ;
    }
    arr[left] = pivotKey;//将pivotKey放置到arr[left]处
    return left;
    
}

实现快速排序的全局操作代码为:

public static void quickSort(int[] arr, int left , int right){
    int pivotIndex ; //枢纽量放在数组的哪个位置,以切割数组
    if(left < right){
        pivotIndex = divideArr(arr , left , right);
        //通过以上的divideArr方法确定出枢纽量最后归位的位置
        quickSort(arr , left , pivotIndex - 1 ); //对枢纽量左边的数进行快速排序
        quickSort(arr , pivotIndex + 1 , right); //对枢纽量右边的数进行快速排序
    }
}

⑥ 归并排序:

归并排序的一个特点,就是要借助辅助数组,这表明了归并排序的复杂度往往比较大。

实现归并排序的关键在于归并操作,以下是归并操作的代码,解析详细:

/**
*    该方法实现将arr[left....mid]与arr[mid+1....right]两个数列排序成一个数列
*/
public static void merge(int[] arr , int left , int mid , int right){
    int[] temp = new int[arr.length];//创建一个辅助数组,用来装归并后的数列
    int p1 = left ,p2 = mid + 1;  //p1和p2作为数组两个区间的比较指针
    int k = left;    //k指针将元素按序存放到temp数组中
    
    //(1)将arr[left....mid]与arr[mid+1....right]两个数列的元素进行逐一比较
    while(p1 <= left && p2 <= right){
        if(arr[p1] <= arr[p2])   
            temp[k++] = arr[p1];
        else
            temp[k++] = arr[p2];
    }   
    
    //(2)如果一个序列的元素没有检测完,则直接把这个数列后面所有的元素加到合并的序列中
    while(p1 <= left)    temp[k++] = temp[p1++];
    while(p2 <= right)   temp[k++] = temp[p2++];

    //(3)将辅助数组里的数据复制回原数组
    for(int i = left ; i <= right ; i ++ )
        arr[i] = temp[i];
   
}

归并排序的全局代码如下:

//实现归并排序(递归调用)
public static void mergeSort(int[] arr, int left ,int right){
    if(left < right){
        int mid = (left + right) / 2;
        mergeSort(arr,left, mid);//对左侧进行递归排序
        mergeSort(arr,mid+1 , right);//对右侧进行递归排序
        merge(arr,left,mid,right);//对左右两侧进行归并操作
    }
}

⑦ 基数排序:

几点说明:

1、基数排序是对传统桶排序的扩展,速度很快。在大规模数据的情形下,甚至比快速排序更快

2、基数排序是经典的用空间换时间的排序方式,因此占用内存会比较大,对海量数据进行排序时,容易出现OutOfMemoryError的异常。

3、基数排序是稳定的排序。

4、对于有负数的数组,不能用基数排序进行排序(桶数组会越界)。如果非要进行负数排序,就要考虑绝对值和运算值反转等操作。

思路:

(1)定义一个二维数组int bucket[10 ][ ],表示10个“桶”(0-9),每个桶就是一个一维数组。为了防止数据在放入桶时溢出,每个一维数组(桶)的大小 定义为 排序数组arr[  ]的长度,即arr.length

(2)同时,为了记录10个桶中的每个桶中存放了多少个数据,需要定义一个一维数组int bucketElementCount[ ]来记录 该次排序中每个桶放入了多少个数据比如 bucketElementCount[ 0 ] 表示 桶bucket[ 0 ]中放入数据的个数。

(3)此时按照个==>十==>百==>千==>万.....的顺序进行多轮排序。排序的轮次数等于最大数的位数。

遍历arr数组,取出arr中的每个元素,根据当前位(从个位开始)的值,将其放入二维数组bucket对应的桶里;

② 遍历二维数组bucket[ ][ ]中 存在元素的“桶”严格按顺序 取出桶中的元素,重新放回arr中

(4)最后,为了获取数组中最大数的位数,可以编写maxRadix(int [ ] arr)方法,返回arr数组中最大元素的位数。

 

public static void radixSort(int[] arr) {		
		int maxLength = maxRadix(arr);
		// 定义一个二维数组,表示10个桶(0-9),每个桶就是一个一维数组
		/*
		 * 说明: 1、二维数组包含10个一维数组 2、为了防止数据在放入桶时溢出,每个一维数组(桶)的大小设置为arr.length
		 * 3、很明显,基数排序是用空间换时间的经典算法
		 */
		int[][] bucket = new int[10][arr.length];

		/*
		 * 为了记录每个桶中实际存放了多少个数据, 需要定义一个一维数组来记录每个桶每一次排序放入的数据个数
		 * 比如:bucketElementCounts[0]表示bucket[0]桶中放入数据个数
		 */
		int[] bucketElementCounts = new int[10];
            //按照个十百千万的顺序进行多轮排序
		for (int w = 0, n = 1; w < maxLength; w++, n *= 10) {
			// ① 遍历arr数组,取出arr中每个元素,根据个位的值,将其放入相应的桶中
			for (int j = 0; j < arr.length; j++) {
				int digitElement = arr[j] / n % 10; // 取出该元素的个位
				bucket[digitElement][bucketElementCounts[digitElement]] = arr[j];
				bucketElementCounts[digitElement]++;
				// 难点:
			}

			// ② 遍历二维bucket中有元素的桶,按顺序取出桶中的元素,重新放回arr
			int index = 0;
			for (int k = 0; k < bucketElementCounts.length; k++) {
				if (bucketElementCounts[k] != 0) {
					for (int i = 0; i < bucketElementCounts[k]; i++) {
						arr[index] = bucket[k][i];
						index++;
					}
					bucketElementCounts[k] = 0;
					for (int i = 0; i < bucket[k].length; i++)
						bucket[k][i] = 0;
				}
				// 进行完一轮排序后,要将其置零,否则下一轮排序会出问题
			}
			 System.out.println("第" + (w+1) + "轮排序的结果为: " +
			 Arrays.toString(arr));
		}
	}

	// 编写一个方法,获取一个数组中最大元素的位数
	public static int maxRadix(int[] arr) {
		int max = arr[0];
		for (int i = 0; i < arr.length; i++)
			if (max < arr[i])
				max = arr[i];
		int maxLength = (max + "").length();
		return maxLength;
	}

⑧ 堆排序:

基本思想:

(1)将待排序序列构造成一个大顶堆

(2)此时,整个序列的最大值就是堆顶的根节点

(3)将其与末尾元素进行交换,此时末尾就是最大值

(4)然后将剩余n-1个元素重新构造成一个堆,就会得到n个元素的次小值,如此反复,就可以得到一个有序序列了。

注意:

(1)堆排序的前提是构造大(小)顶堆,所以首先要编写一个构造堆的方法,此处构造大顶堆。

(2)由于在堆排序中,排序数据构成一棵完全二叉树。对于完全二叉树,结点序号从0开始,则序号为 i 的结点,其左孩子(假设存在)的标号为 2 * i + 1 右孩子(假设存在)的标号为 2 * i + 2

(3)构造大顶堆的方法:

	// 堆排序的前提是构造大(小)顶堆,所以在此编写一个相应的构造堆的方法
	/**
	 * 功能: 完成将 i指向的非叶子结点为根的树 调整成堆(大顶堆)
	 * 
	 * @param arr
	 *            待调整的数组
	 * @param i
	 *            表示非叶子结点在数组中的索引(在堆排序中,从 第1个非叶子结点 开始调整)
	 * @param length
	 *            表示对多少个元素继续进行调整,length自然是不断减少的
	 */
	public static void adjustHeap(int arr[], int i, int length) {
		int temp = arr[i];// 首先取出当前元素的值,保存在临时变量中
		// 此时开始调整
		for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {
			// k = i * 2 + 1 表示k从i的左子结点出发,不断地从“左指针域”去寻找 最大值,所以也要k = k * 2 + 1
			if (k + 1 < length && arr[k] < arr[k + 1]) {
				k++; // 走到了当前i的左结点后,要先拿该左结点与i的右结点作比较,寻找一个更大的值
				// 所以如果右结点要大于左结点,那就把指针k移动到i的右结点,然后继续从左指针域去找最大值。
			}
			if (arr[k] > temp) { // 如果i节点的左(右)孩子值大于i的值,就把更大的值赋给i
				arr[i] = arr[k];
				i = k; // !!!i指向k是为了持续以上的for循环,继续向下比较
			} else {
				break;
				// 为什么这里敢break?
				// 因为我们对一棵树的调整是从下到上,从左往右的,所以调整当前结点时,它的后辈结点一定已经调整好了
				// 即该结点的孩子结点对应的子树已经符合大顶堆的性质了
			}
		}
		arr[i] = temp;
		// for循环结束后,以i为根结点的树的最大值,已经放在了i的位置(顶部)。
		//这一句相当于将原来 根结点i的值 替换到
	}

在构造大顶堆方法的基础上,从最后一个非叶子结点开始倒序 进行建堆。 

建堆完成后,将最大元素与数组末尾元素交换位置。

由于交换了位置,因此破坏了以前堆的性质,因此要再进行一次堆的调整。 

此时调整堆只需要从根节点开始(原因见以下代码的注释,很详细)

	// 编写一个堆排序的方法

	public static void heapSort(int arr[]) {
		System.out.println("堆排序开始:");
		// 1、首先将无序序列构建成一个堆
		for (int i = arr.length / 2 - 1; i >= 0; i--) {
			adjustHeap(arr, i, arr.length); // 这里是初始化建堆操作
		}
		int temp;// 仅仅用于交换操作
		// 2、进行了建堆操作后,将堆顶元素和末尾元素进行交换,将最大元素放到数组末端;
		// 3、重新调整结构,在交换后再次建堆,然后继续进行上述交换,反复执行,使整个序列有序
		for (int j = arr.length - 1; j > 0; j--) {
			temp = arr[j];
			arr[j] = arr[0];
			arr[0] = temp;
			adjustHeap(arr, 0, j);
			// 这里是由于将堆顶元素和堆底元素进行交换后,堆的性质被破坏。
			// 因此需要将堆顶元素向下进行调整,使其继续保持堆的性质。
			// 之所以这里从0开始调整即可,是因为堆顶和堆底元素交换后,堆底元素被输出。
			// 此时除了堆顶元素即0处元素,其他元素构成的树依然满足堆的性质。
		}
		System.out.println("排序完成后的数组为: " + Arrays.toString(arr));

	}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值