【leetcode算法面试】leetcode题目5-排序算法

一. 冒泡排序

    冒泡排序(BubbleSort)的基本概念是:依次比较相邻的两个数,将小数放在前面,大数放在后面

package main

import "fmt"

func main() {
	fmt.Println("vim-go")
	var nums = []int{3, 9, 10, 2, 1, 100, 4, 8}
	bubbleSort(nums)
	fmt.Printf("%v\n", nums)
}

func bubbleSort(nums []int) {
	var flag = false
	for i := 0; i < len(nums); i++ {
		flag = false
		for j := 0; j < len(nums)-i-1; j++ {
			if nums[j] > nums[j+1] {
				flag = true
				nums[j], nums[j+1] = nums[j+1], nums[j]
			}
		}
		if !flag {
			break
		}
	}
}

二. 选择排序

      择排序的原理是,对给定的数组进行多次遍历,每次均找出最大的一个值的索引

func selectSort(nums []int) {
	n := len(nums)
	for i := 0; i < n; i++ {
		var maxIndex = 0
		for j := 1; j < n-i; j++ {
			if nums[j] > nums[maxIndex] {
				maxIndex = j
			}
		}
		nums[n-1-i], nums[maxIndex] = nums[maxIndex], nums[n-1-i]
	}
}

 

三 快速排序

对挖坑填数进行总结

1.i =L; j = R; 将基准数挖出形成第一个坑a[i]。

2.j--由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。

3.i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。

4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中。

     照着这个总结很容易实现挖坑填数的代码:

func quickSort(nums []int, left, right int) {
	if left < right {
		privot := adjustNums(nums, left, right)
		quickSort(nums, left, privot-1)
		quickSort(nums, privot+1, right)
	}
}

func adjustNums(nums []int, left, right int) int {
	var l, r, privot = left, right, nums[left]
	for l < r {
		fmt.Printf("privot: %v l: %v r: %v left: %v right: %v\n", privot, l, r, left, right)
		for l < r && privot < nums[r] {
			r--
		}
		if l < r {
			nums[l] = nums[r]
			l++
		}
		for l < r && privot > nums[l] {
			l++
		}
		if l < r {
			nums[r] = nums[l]
			r--
		}
	}
	nums[l] = privot
	return l
}

 

 

四. 插入排序

从第二个数开始向右侧遍历,每次均把该位置的元素移动至左侧,放在放在一个正确的位置(比左侧大,比右侧小)。

func insertSort(nums []int) {
	for i := 1; i < len(nums); i++ {
		if nums[i] < nums[i-1] {
			var j = i - 1
			var cur = nums[i]
			for j >= 0 && nums[j] > cur {
				nums[j+1] = nums[j]
				j--
			}
			nums[j+1] = cur
		}
	}
}

 

 

一. 堆排序

堆顶到叶子的筛选过程,应该刚开始是堆由于把堆顶给换了,罪魁祸首是堆顶,其它小范围还是堆,所以是从堆顶开始

建堆是一个从下往上进行“筛选”的过程 (首先要把底部的建成小堆,前面调整是因为只有堆顶,其它都已经是堆了。当我建堆到堆顶是也是从堆顶往下筛选)(所以说建堆大范围是从下往上筛选,在添加该结点时,还得从该节点往下筛选确保添加该节点后还是堆)。

 

1. 堆的存储

 

一般都用数组来表示堆,i结点的父结点下标就为(i – 1) / 2。它的左右子结点下标分别为2 * i + 1和2 * i + 2。如第0个结点左右子结点下标分别为1和2。

2. 堆的操作——插入删除

下面先给出《数据结构C++语言描述》中最小堆的建立插入删除的图解,再给出本人的实现代码,最好是先看明白图后再去看代码。

3. 堆的插入

每次插入都是将新数据放在数组最后。可以发现从这个新数据的父结点到根结点必然为一个有序的数列,现在的任务是将这个新数据插入到这个有序数据中

 

插入时:

[cpp] view plaincopy

  1. //在最小堆中加入新的数据nNum  
  2. void MinHeapAddNumber(int a[], int n, int nNum)  
  3. {  
  4.     a[n] = nNum;  
  5.     MinHeapFixup(a, n);
  6. }

 

[cpp] view plaincopy

  1. //  新加入i结点  其父结点为(i - 1) / 2  
  2. void MinHeapFixup(int a[], int i)  
  3. {  
  4.     int j, temp;  
  5.       
  6.     temp = a[i];  
  7.     j = (i - 1) / 2;      //父结点  
  8.     while (j >= 0 && i != 0)  
  9.     {  
  10.         if (a[j] <= temp)  
  11.             break;  
  12.           
  13.         a[i] = a[j];     //把较大的子结点往下移动,替换它的子结点  
  14.         i = j;  
  15.         j = (i - 1) / 2;  
  16.     }  
  17.     a[i] = temp;  
  18. }  

 

 

4. 堆的删除

 

按定义,堆中每次都只能删除第0个数据。为了便于重建堆,实际的操作是将最后一个数据的值赋给根结点,然后再从根结点开始进行一次从上向下的调整。调整时先在左右儿子结点中找最小的,如果父结点比这个最小的子结点还小说明不需要调整了,反之将父结点和它交换后再考虑后面的结点。相当于从根结点将一个数据的“下沉”过程。

[cpp] view plaincopy

  1. //  从i节点开始调整,n为节点总数 从0开始计算 i节点的子节点为 2*i+1, 2*i+2  
  2. void MinHeapFixdown(int a[], int i, int n)  
  3. {  
  4.     int j, temp;  
  5.   
  6.     temp = a[i];  
  7.     j = 2 * i + 1;  
  8.     while (j < n)  
  9.     {  
  10.         if (j + 1 < n && a[j + 1] < a[j]) //在左右孩子中找最小的  
  11.             j++;  
  12.   
  13.         if (a[j] >= temp)  
  14.             break;  
  15.   
  16.         a[i] = a[j];     //把较小的子结点往上移动,替换它的父结点  
  17.         i = j;  
  18.         j = 2 * i + 1;  
  19.     }  
  20.     a[i] = temp;  
  21. }  
  22. //在最小堆中删除数  
  23. void MinHeapDeleteNumber(int a[], int n)  
  24. {  
  25.     Swap(a[0], a[n - 1]);  
  26.     MinHeapFixdown(a, 0, n - 1);  
  27. }  

 

5. 堆化数组

有了堆的插入和删除后,再考虑下如何对一个数据进行堆化操作。要一个一个的从数组中取出数据来建立堆吧,不用!先看一个数组,如下图:

很明显,对叶子结点来说,可以认为它已经是一个合法的堆了即20,60, 65, 4, 49都分别是一个合法的堆。只要从A[4]=50开始向下调整就可以了。然后再取A[3]=30,A[2] = 17,A[1] = 12,A[0] = 9分别作一次向下调整操作就可以了。下图展示了这些步骤:

写出堆化数组的代码:

[cpp] view plaincopy

  1. //建立最小堆  
  2. void MakeMinHeap(int a[], int n)  
  3. {  
  4.     for (int i = n / 2 - 1; i >= 0; i--)  
  5.         MinHeapFixdown(a, i, n);  
  6. }  


至此,堆的操作就全部完成了(注1),再来看下如何用堆这种数据结构来进行排序。

堆排序

首先可以看到堆建好之后堆中第0个数据是堆中最小的数据。取出这个数据再执行下堆的删除操作。这样堆中第0个数据又是堆中最小的数据,重复上述步骤直至堆中只有一个数据时就直接取出这个数据。

由于堆也是用数组模拟的,故堆化数组后,第一次将A[0]与A[n - 1]交换,再对A[0…n-2]重新恢复堆。第二次将A[0]与A[n – 2]交换,再对A[0…n - 3]重新恢复堆,重复这样的操作直到A[0]与A[1]交换。由于每次都是将最小的数据并入到后面的有序区间,故操作完成后整个数组就有序了。有点类似于直接选择排序

[cpp] view plaincopy

  1. void MinheapsortTodescendarray(int a[], int n)  
  2. {  
  3.     for (int i = n - 1; i >= 1; i--)  
  4.     {  
  5.         Swap(a[i], a[0]);  
  6.         MinHeapFixdown(a, 0, i);  
  7.     }  
  8. }  

注意使用最小堆排序后是递减数组,要得到递增数组,可以使用最大堆。

由于每次重新恢复堆的时间复杂度为O(logN),共N - 1次重新恢复堆操作,再加上前面建立堆时N / 2次向下调整,每次调整时间复杂度也为O(logN)。二次操作时间相加还是O(N * logN)。故堆排序的时间复杂度为O(N * logN)。

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值