常用的排序算法汇总(更新中)

常用的排序算法汇总(更新中)

Louis 880 2021-04-26

面试常问的手撕算法——排序类题目。由于不是科班出身,对于排序算法的理解仅限于冒泡法,对于一些复杂度较低的排序算法没怎么了解。趁着这个机会将常用的都汇总一下,尝试自己实现一遍吧。

开始之前设计一些工具

对数组进行排序,需要使用一些随机数工具生成随机数组

static vector<int> generateRandomArray(int size, int min, int max) {
  vector<int> ret;
  uniform_int_distribution<unsigned> u(min, max);
  default_random_engine e;
  for (int i = 0; i < size; i++) {
    ret.push_back(u(e));
  }
  return ret;
}

需要工具判断排序是否完成

static bool isSorted(vector<int> &nums) {
  for (int i = 0; i < nums.size() - 1; i++) {
    if (nums[i] > nums[i + 1]) {
      return false;
    }
  }
  return true;
}

冒泡排序

冒泡排序要对一个列表多次重复遍历。它要比较相邻的两项,并且交换顺序排错的项。每对列表实行一次遍历,就有一个最大项排在了正确的位置。大体上讲,列表的每一个数据项都会在其相应的位置 “冒泡”。如果列表有 n 项,第一次遍历就要比较 n-1 对数据。需要注意,一旦列表中最大(按照规定的原则定义大小)的数据是所比较的数据对中的一个,它就会沿着列表一直 后移,直到这次遍历结束。

static void bubbleSort(vector<int> &nums) {
  for (unsigned long i = nums.size() - 1; i >= 0; i--) {
    for (int j = 0; j <= i; j++) {
      if (nums[j] > nums[j + 1]) {
        swap(nums[j], nums[j + 1]);
      }
    }
  }
}

选择排序

选择排序提高了冒泡排序的性能,它每遍历一次列表只交换一次数据,即进行一次遍历时找 到最大的项,完成遍历后,再把它换到正确的位置。和冒泡排序一样,第一次遍历后,最大的数 据项就已归位,第二次遍历使次大项归位。这个过程持续进行,一共需要 n-1 次遍历来排好 n 个数 据,因为最后一个数据必须在第 n-1 次遍历之后才能归位。

static void selectionSort(vector<int> &nums) {
  for (int i = 0; i < nums.size(); i++) {
    int min_index = i;
    for (int j = i + 1; j < nums.size(); j++) {
      if (nums[j] < nums[min_index]) {
        min_index = j;
      }
    }
    swap(nums[min_index], nums[i]);
  }
}

插入排序

插入排序的算法复杂度仍然是公式,但其工作原理稍有不同。它总是保持一个位置靠前的已排好的子表,然后每一个新的数据项被 “插入” 到前边的子表里,排好的子表增加一项。我们认为只含有一个数据项的列表是已经排好的。每排后面一个数据(从 1 开始到 n-1),这个的数据会和已排好子表中的数据比较。比较时,我们把之前已经排好的列表中比这个数据大的移到它的右边。当子表数据小于当前数据,或者当前数据已经和子表的所有数据比较了时,就可 以在此处插入当前数据项。

static void insertionSort(vector<int> &nums) {
  for (int i = 1; i < nums.size(); i++) {
    int cur = nums[i];
    int position = i;
    while (nums[position - 1] > cur && position > 0) {
      nums[position] = nums[position - 1];
      position--;
    }
    nums[position] = cur;
  }
}

希尔排序

希尔排序有时又叫做 “缩小间隔排序”,它以插入排序为基础,将原来要排序的列表划分为一些子列表,再对每一个子列表执行插入排序,从而实现对插入排序性能的改进。划分子列的特定方法是希尔排序的关键。我们并不是将原始列表分成含有连续元素的子列,而是确定一个划分列表的增量 “i”,这个i更准确地说,是划分的间隔。然后把每间隔为i的所有元素选出来组成子列表,然后对每个子序列进行插入排序,最后当 i=1 时,对整体进行一次直接插入排序。

static void shellSort(vector<int> &nums) {
  int n = nums.size();
  int gap = n / 2;
  while (gap > 0) {
    for (int i = 0; i <= gap; i++) {
      gapInsertionSort(nums, i, gap);
    }
    gap /= 2;
  }
}

static void gapInsertionSort(vector<int> &nums, int startpos, int gap) {
  for (int i = startpos + gap; i < nums.size(); i += gap) {
    int position = i;
    int currentvalue = nums[i];
    while (position > startpos && nums[position - gap] > currentvalue) {
      nums[position] = nums[position - gap];
      position -= gap;
    }
    nums[position] = currentvalue;
  }
}

归并排序

快速排序

参考网站:

常用的排序算法总结 - 知乎 (zhihu.com)