数据结构笔记(3):堆和堆排序

堆的定义:堆首先必须是一棵完全二叉树,且这棵完全二叉树中的每个结点总是不大于(或不小于)其父结点。

 

要想完成堆排序首先要建立一个堆,而建立一个堆的关键就是shiftUp和shiftDown操作:

  • shiftUp:这里以大顶堆为例
    • 假设一个堆一开始如下图所示
  •       现在我们往堆中插入52这个元素,如下图所示
  •     shiftUp的核心思想就是:就是将新插入的元素与它的父结点进行比较,若它的值比父结点的大,则交换他们两个的位置,直到该结点的值小于其父结点,或者已经到达根结点为止。如下图所示

所以我们很容易就可以写出shiftUp的核心代码:

void MaxHeap::shiftUp(int k) {
  int tmp = data[k];
  while (k > 1 && tmp > data[k / 2]) {
    data[k] = data[k / 2];
    k /= 2;
  }
  data[k] = tmp;
}
  •  shiftDown:
    • 我们每次从堆中取出一个元素,只能取出根结点的元素,当我们从堆中取出一个元素之后就需要将堆中的最后一个元素放到根结点,即放到第一个元素,如下图所示

  •  将最后一个元素放到根结点后,就要进行shiftDown操作,shiftDown操作的核心思想就是将根结点的元素一步一步向下挪动,直到找到它合适的位置,如下图所示。

 我们很容易就可以得到shiftDown的代码

void MaxHeap::shiftDown(int k) {
  int tmp = data[k];
  int j = k * 2;
  while (j <= count) {
    if (j + 1 <= count && data[j + 1] > data[j]) {
      j++;
    }
    if (tmp < data[j]) {
      data[k] = data[j];
    } else {
      break;
    }
    k = j;
    j = k * 2;
  }
  data[k] = tmp;
}

 

只要完成了shiftUp和shiftDown操作之后,堆的核心操作就完成了;而堆排序的就是每次取出堆顶的元素之后,再进行一次shiftDown操作即可。

下面我们给出建立大顶堆的代码:

MaxHeap.h

//
// Created by hansy2015 on 2020/8/30.
//

#ifndef HEAPSORT_MAXHEAP_H
#define HEAPSORT_MAXHEAP_H

class MaxHeap {
public:
  MaxHeap();
  MaxHeap(int n);
  MaxHeap(int arr[], int n);
  ~MaxHeap();

  void Insert(int);
  int size();
  bool isEmpty();
  void Print();
  int get();
private:
  void shiftUp(int);
  void shiftDown(int);
private:
  int capaity;
  int count;
  int* data;
};


#endif //HEAPSORT_MAXHEAP_H

MaxHeap.cpp

//
// Created by hansy2015 on 2020/8/30.
//

#include "MaxHeap.h"
#include <iostream>
#include <cassert>

using namespace  std;


MaxHeap::MaxHeap() {}
MaxHeap::MaxHeap(int n) : capaity(n), count(0) {
  data = new int[n + 1];
}
MaxHeap::MaxHeap(int *arr, int n) {
  data = new int[n + 1];
  for (int i = 0; i < n; i++) {
    data[i + 1] = arr[i];
  }
  count = n;
  capaity = n;
  for (int i = count / 2; i >= 1; i--) {
    shiftDown(i);
  }
}
MaxHeap::~MaxHeap() {
  delete [] data;
}

int MaxHeap::size() {
  return count;
}

bool MaxHeap::isEmpty() {
  return 0 == count;
}

void MaxHeap::Insert(int item) {
  assert(count + 1 <= capaity);
  data[count + 1] = item;
  count++;
  shiftUp(count);
}

void MaxHeap::shiftUp(int k) {
  int tmp = data[k];
  while (k > 1 && tmp > data[k / 2]) {
    data[k] = data[k / 2];
    k /= 2;
  }
  data[k] = tmp;
}

void MaxHeap::Print() {
  for (int i = 1; i <= count; i++) {
    cout << data[i] << " ";
  }
  cout << endl;
}

int MaxHeap::get() {
  assert(count > 0);
  int tmp = data[1];
  swap(data[1], data[count]);
  count--;
  shiftDown(1);
  return tmp;
}

void MaxHeap::shiftDown(int k) {
  int tmp = data[k];
  int j = k * 2;
  while (j <= count) {
    if (j + 1 <= count && data[j + 1] > data[j]) {
      j++;
    }
    if (tmp < data[j]) {
      data[k] = data[j];
    } else {
      break;
    }
    k = j;
    j = k * 2;
  }
  data[k] = tmp;
}

最后我们在main函数中对比一下堆排序和快速排序的性能消耗

void heapSort(int arr[], int n) {
  MaxHeap maxHeap(arr, n);
  for (int i = n - 1; i >= 0; i--) {
    arr[i] = maxHeap.get();
  }
}




void insertionSort(int arr[], int l, int r) {
  for (int i = l + 1; i <= r; i++) {
    int tmp = arr[i];
    int j;
    for (j = i - 1; j >= l; j--) {
      if (tmp < arr[j]) {
        arr[j + 1] = arr[j];
      } else {
        break;
      }
    }
    arr[j + 1] = tmp;
  }
}

void __partition(int arr[], int l, int r, int& lt, int& gt) {
  srand(time(0));
  int x = (rand() % (r - l + 1) + l);
  swap(arr[l], arr[x]);
  int v = arr[l];

  lt = l;
  gt = r + 1;
  int i = l + 1;
  // arr[l + 1 ... lt] < v && arr[lt + 1 ... i - 1] = v && arr[gt ... r] > v
  while (i < gt) {
    if (arr[i] == v) {
      i++;
    } else if (arr[i] < v) {
      swap(arr[lt + 1], arr[i]);
      lt++;
      i++;
    } else {
      swap(arr[gt - 1], arr[i]);
      gt--;
    }
  }
  swap(arr[l], arr[lt]);
  // [l ... lt - 1] && [lt .. gt - 1] && [gt ... r]
}



void __quickSort3Ways(int arr[], int l, int r) {
  if (r - l <= 15) {
    insertionSort(arr, l, r);
    return;
  }
  int lt, gt;
  __partition(arr, l, r, lt, gt);
  __quickSort3Ways(arr, l, lt - 1);
  __quickSort3Ways(arr, gt, r);
}

void quickSort3Ways(int arr[], int n) {
  __quickSort3Ways(arr, 0, n - 1);
}


int main() {
  SortTestHelper sortTestHelper;
  int* arr = sortTestHelper.generateRandomArray(1000000, 0, 1000000);
  int* arr1 = sortTestHelper.copyIntArray(arr, 1000000);

  sortTestHelper.testSort("heapSort", heapSort, arr, 1000000);
  sortTestHelper.testSort("quickSort", quickSort3Ways, arr1, 1000000);
  delete [] arr;
  delete [] arr1;
  return 0;
}

li

可以看出在1000000的数据量时,快速排序的性能时优于堆排序的,所以堆这种数据结构我们一般不用它来 排序,大部分情况下,我们用它来动态的维护一些数据,例如我们用堆来作为优先队列底层实现。

 

 

好了,到这里关于排序算法我们基本就介绍完了,下面我们做一个总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值