堆数据结构实际上是一种数组对象,是以数组的形式存储的,但是它可以被视为一颗完全二叉树,因此又叫二叉堆。堆分为以下两种类型:
大顶堆:父结点的值不小于其子结点的值,堆顶元素最大
小顶堆:父结点的值不大于其子结点的值,堆顶元素最小
堆排序的时间复杂度跟合并排序一样,都是O(nlgn),但是合并排序不是原地排序(原地排序:在排序过程中,只有常数个元素是保存在数组以外的空间),合并排序的所有元素都被拷贝到另外的数组空间中去,而堆排序是一个原地排序算法。
1、在堆排序中,我们通常使用大顶堆来实现,由于堆在操作上是被看着一颗完全二叉树,所以其高度为lgn,堆结构上的一些操作的时间复杂度也通常是O(lgn)。
/* * 算法导论 第六章 堆排序 * 堆数据结构的实际存储是作为一个顺序数组来保存的 * 对堆的操作是将它作为一个完全二叉树的结构来使用的 * * 堆排序也是属于一种选择排序,不过相比简单选择排序(时间复杂度为O(n^2)),堆排序要快得多 * 堆排序分为以下几个步骤: * 首先是建立大顶堆,即函数buildMaxHeap,建堆实际上是利用堆的最大化调整(maxHeapify)自底向上来实现的 * 然后是逐步将堆顶的最大元素交换到堆的结尾,堆的大小也不断缩小,然后再将堆最大化,从而实现排序 * * 其中建堆的时间复杂度为O(n),堆的最大化调整时间复杂度为O(lgn),所以总的 * 时间复杂度是O(n*lgn+n),即O(nlgn) */ #include <iostream> using namespace std; void printArray(int arr[], int len); void heapSort(int arr[], int len); void maxHeapify(int arr[], int heapSize, int pos); void buildMaxHeap(int arr[], int len); void selectSort(int *arr, int len); void exchange(int arr[], int i, int j); int main() { int arr[] = {16, 14, 10, 8, 7, 9, 3, 2, 4, 1}; int len = sizeof(arr) / sizeof(arr[0]); cout << "原数组:" << endl; printArray(arr, len); heapSort(arr, len); //selectSort(arr, len); cout << "排序后的数组:" << endl; printArray(arr, len); return 0; } void printArray(int arr[], int len) { for (int i=0; i<len; i++) { cout << arr[i] << " "; } cout << endl; } void heapSort(int arr[], int len) { buildMaxHeap(arr, len); for (int i=len-1; i>0; i--) { int temp = arr[0]; arr[0] = arr[i]; arr[i] = temp; maxHeapify(arr, i, 0); } } void maxHeapify(int arr[], int heapSize, int pos) { int lPos = (pos + 1) * 2 - 1; int rPos = (pos + 1) * 2; int largest = pos; if (lPos < heapSize && arr[lPos] > arr[largest]) largest = lPos; if (rPos < heapSize && arr[rPos] > arr[largest]) largest = rPos; if (largest != pos) { int temp = arr[pos]; arr[pos] = arr[largest]; arr[largest] = temp; maxHeapify(arr, heapSize, largest); } } void buildMaxHeap(int arr[], int len) { for (int i=len/2-1; i>=0; i--) { maxHeapify(arr, len, i); } } /* * 简单选择排序 * 每次经过 n-i 次比较,从序列中选出i之后的最小元素放在第 i 个位置,以此排序 * 时间复杂度为O(n^2) */ void selectSort(int *arr, int len) { for (int i=0; i<len; i++) { int minIndex = i; for (int j=i+1; j<len; j++) { if (arr[j] < arr[minIndex]) minIndex = j; } if (minIndex != i) { exchange(arr, i, minIndex); } } } void exchange(int arr[], int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }
2、堆结构可以用来实现优先级队列,优先级队列是一组元素构成的集合,可以从中取出最大或者最小的元素,堆是优先级队列的一种很好的实现。通过堆,优先级队列上的任意操作可以再O(lgn)时间内实现。
3、习题6.5-8解答
/* * 算法导论 第六章 习题6.5-8 * * 将k个链表中的首元素取出来作为一个数组,构成一个最小堆,堆顶元素即为最小 * 每次将堆顶元素插入到新链表尾部,然后将该元素原来所在链表的下一元素取出放到堆顶 * 若该链表取完了,则直接将堆尾元素放到堆顶,并将堆的大小减1,调整堆,重复取出堆顶 * 最小元素插入到新链表,直到k个链表都为空了为止 * * 时间复杂度分析:建堆为O(k),取出堆中最小元素为O(lgk),共取了n次 * 时间复杂度为O(k+nlgk)=O(nlgk) */ #include <iostream> using namespace std; typedef struct LinkNode { int key; LinkNode *next; } LinkNode; LinkNode* createLink(int arr[], int len); LinkNode* heapExtractMin(LinkNode* arr[], int &len); void minHeapify(LinkNode* arr[], int heapSize, int pos); void buildMinHeap(LinkNode* arr[], int len); int main() { int k = 5, n = 20; int arr1[3] = {11, 14, 67}; int arr2[2] = {5, 35}; int arr3[5] = {3, 8, 12, 25, 90}; int arr4[4] = {9, 21, 49, 73}; int arr5[6] = {1, 32, 33, 45, 53, 88}; LinkNode *link1 = createLink(arr1, 3); LinkNode *link2 = createLink(arr2, 2); LinkNode *link3 = createLink(arr3, 5); LinkNode *link4 = createLink(arr4, 4); LinkNode *link5 = createLink(arr5, 6); /* * 把堆定义成一个指针数组,需要注意指针数组与数组指针的区别 * 指针数组:定义的是一个数组,数组中的每一个元素都是一个指针 * 数组指针:定义的是一个指针,这个指针指向一个数组 */ //定义堆数组 LinkNode* heap[] = {link1, link2, link3, link4, link5}; buildMinHeap(heap, k); //定义重新排序后的链表 LinkNode *resultLink = heapExtractMin(heap, k); LinkNode *beforeNode = resultLink; while (beforeNode && n-- > 1) { LinkNode *node = heapExtractMin(heap, k); beforeNode->next = node; beforeNode = node; } while (resultLink) { cout << resultLink->key << " "; LinkNode *node = resultLink; resultLink = resultLink->next; delete node; } cout << endl; return 0; } LinkNode* createLink(int arr[], int len) { LinkNode *nextNode = NULL; for (int i=len-1; i>=0; i--) { LinkNode *node = new LinkNode(); node->key = arr[i]; node->next = nextNode; nextNode = node; } return nextNode; } LinkNode* heapExtractMin(LinkNode* arr[], int &len) { if (len < 1) return NULL; LinkNode *minNode = arr[0]; if (arr[0]->next) { arr[0] = arr[0]->next; } else { len--; arr[0] = arr[len]; } if (len > 1) { minHeapify(arr, len, 0); } return minNode; } void minHeapify(LinkNode* arr[], int heapSize, int pos) { int lPos = (pos + 1) * 2 - 1; int rPos = (pos + 1) * 2; int smallest = pos; if (lPos < heapSize && arr[lPos]->key < arr[smallest]->key) smallest = lPos; if (rPos < heapSize && arr[rPos]->key < arr[smallest]->key) smallest = rPos; if (smallest != pos) { LinkNode* temp = arr[pos]; arr[pos] = arr[smallest]; arr[smallest] = temp; minHeapify(arr, heapSize, smallest); } } void buildMinHeap(LinkNode* arr[], int len) { for (int i=len/2-1; i>=0; i--) { minHeapify(arr, len, i); } }