note_19:部分查找和排序

8 篇文章 0 订阅

部分查找和排序


参考

  • 《2019年数据结构考研复习指导》王道论坛·组编,电子工业出版社
  • 百度百科:直接插入排序


1. 查找

(1)折半查找

  • 折半查找,又称为二分查找,它仅适用于有序的顺序表。
function binary_search(arr, target) {
  var low = 0, high = arr.length - 1, mid; // high是数组长度-1,因为这是最后一位
  var count = 0;
  while (low <= high) {  // 等于的情况也可以,最后有可能会出现指向同一个元素
    count++;
    mid = Math.floor((low + high) / 2); // 如果直接除以2,出来是小数,计算失败
    if (arr[mid] == target) { // 先判断等不等于要找的数再进行下面的流程
      console.log(count); // 3
      return mid;
    }  else if (arr[mid] > target) { // 如果中间值大于要找的数,那么就往左找
      high = mid - 1;
    } else {  // 如果中间值小于要找的数,那么就往右找
      low = mid + 1;
    } 
  }
  console.log(count);
  return -1;
}

(function main() {
  var arr = [7, 10, 13, 16, 29, 32, 33, 37, 41, 43];
  var target = 41;
  var result = binary_search(arr, target);
  console.log(result); // 8
}());

2. 排序

(1)插入排序

① 直接插入排序

在排序过程中,待排序表L[1…n]在某次排序过程中的某一时刻状态如下:(递增有序序列)
在这里插入图片描述
为了实现将元素L(i)插入到已有序的子序列L[1…i-1]中,我们需要执行以下操作(为避免混淆,下面用“L[ ]”表示一个表,而用“L( )”表示一个元素):

  1. 查找出L(i)在L[1…i]中的插入位置k。
  2. 将L[k…i-1]中所有元素全部后移一个位置。
  3. 将L(i)复制到L(k)。
// 获得递增序列
function insertSort(arr, n) {
  for (var i = 1; i < n; i++) { // 因为每一次都要跟前一个元素比,所以要从1开始
    if (arr[i] < arr[i - 1]) {  // 因为默认是递增的,所以如果当前元素比前一个元素小
      var temp = arr[i];       // 那么就需要把当前元素插入到前面的有序列表中
      var j;
      // j--的时候要注意j始终大于等于0
      for (j = i - 1; (j >= 0) && (temp < arr[j]); j--) { // 从前一个元素开始
        arr[j + 1] = arr[j];   // 把它前面的元素全部往后移,直到当前元素比移动的元素大为止
      }
      arr[j + 1] = temp; // 当前元素比要移动的元素大,所以插入到要移动的元素后面,即j+1
    }
  }
  
  console.log(arr); // [1, 12, 14, 34, 67, 76, 80, 89, 98, 109, 190]
}

(function main() {
  var arr = [98, 76, 109, 34, 67, 190, 80, 12, 14, 89, 1];
  var n = arr.length;
  insertSort(arr, n);
}());
② 折半插入排序

先折半查找出元素的待插入为止,然后再统一地移动待插入为止之后的所有元素

// 获得递增序列
function binary_insert(arr, n) { // 折半查找+插入排序
  var low, high, mid;
  for (var i = 1; i < n; i++) {
    var temp = arr[i]; // 不用像直接插入排序那样让当前元素跟前一个元素比较
    low = 0;
    high = i - 1;
    while (low <= high) {
      mid = Math.floor((low + high) / 2); // 折半查找
      if (arr[mid] > temp) high = mid - 1; // 往左边找
      else low = mid + 1; // 往右边找
    }
    // 因为前面折半查找到了最后一次循环时是low和high和mid都相等的时候
    // 1. 如果定位的数字比待插入的数字大,high会在定位数字的左边,high+1正好是定位位置
    //    把定位位置开始的数字后移,将待插入数字放在定位位置
    // 2. 如果定位的数字比待插入的数字小,high+1就是待插入数字的位置,不会移动
    for (var j = i - 1; j >= high + 1; j--) { // high+1就是找到那个位置的后一个
      arr[j + 1] = arr[j];
    }
    arr[high + 1] = temp;
  }
  console.log(arr);  // [1, 12, 14, 34, 67, 76, 80, 89, 98, 109, 190]
}

(function main() {
  var arr = [98, 76, 109, 34, 67, 190, 80, 12, 14, 89, 1];
  var n = arr.length;
  binary_insert(arr, n);
}());

(2)交换排序

① 冒泡排序

假设待排序表长为n,从后往前(或从前往后)两两比较相邻元素的值,若为逆序(即A[i-1] > A[i]),则交换它们,直到序列比较完。

// 获得递增序列
function bubbleSort(arr, n) {
  var i, j;
  for (i = 0; i < n - 1; i++) { // 最后一个不用排,因为到了第n-2轮的时候,前n-2个包括第n-2个已经排好
    var flag = false;
    // 如果写成for (j = i + 1; j < n; j++)就只能排完后半部分的序列
    for (j = n - 1; j > i; j--) { // 每换完一轮,前i个数包括第i个都排好的
      if (arr[j] < arr[j - 1]) { // 交换
        var temp = arr[j];
        arr[j] = arr[j - 1];
        arr[j - 1] = temp;
        flag = true;
      }
    }
    if (!flag) break; // 如果这趟遍历下来没有发生交换,就说明已经排好了
  }
  console.log(arr);
}

(function main() {
  var arr = [98, 76, 109, 34, 67, 190, 80, 12, 14, 89, 1];
  var n = arr.length;
  bubbleSort(arr, n);
}());
② 快速排序

基本思想是基于分治法的:在待排序表L[1…n]中任取一个元素pivot作为基准,通过一趟排序将待排序表划分为独立的两部分L[1…k-1]和L[k+1…n],使得L[1…k-1]中所有元素小于pivot,L[k+1…n]中所有元素大于或等于pivot,则pivot放在了其最终位置L(k)上,这个过程称作一趟快速排序。而后分别递归地对两个字表重复上述过程,直至每部分内只有一个元素或空为止,即所有元素放在了其最终位置上。

// 获得递增序列
function quickSort(arr, low, high) {
  if (low < high) { // 递归出口,不能取等于,因为后面的quickSort()执行起来没意义
    var pivot = partition(arr, low, high); // 划分子表
    quickSort(arr, low, pivot - 1); // 对pivot左边的子表进行排序
    quickSort(arr, pivot + 1, high); // 对pivot右边的子表进行排序
  }
}

function partition(arr, low, high) {
  var pivot = arr[low]; // 取表中第一个元素作为枢轴值
  while (low < high) {
  	// 比枢轴值大的放右边,从后往前遍历时,如果遇到比枢轴值小的就要往前放
  	// 因为第一个元素已经存成pivot了,所以可以直接把数值放到第一位
    while ((low < high) && (arr[high] >= pivot)) high--; // 遇到小过pivot的马上停下
    arr[low] = arr[high];
    // 比枢轴值小的放左边,从前往后遍历时,如果遇到比枢轴值大的就要往后放
    // 因为上面那句代码已经把high位置的元素放在了low位置,所以high位置可以放另一个元素
    // 如果整个arr是有序的话也不会影响,因为上一句把high位置的元素放low位置,后面又换回去了
    while ((low < high) && (arr[low] <= pivot)) low++; // 遇到打过大过pivot的马上停下
    arr[high] = arr[low];
  }
  // 因为前面存起了low位置的pivot,而while里面最后一次交换是将low位置的元素放到high位置
  // 所以这时要把pivot放回low位置
  arr[low] = pivot; 
  return low; // 以low为枢轴划分子表
}

(function main() {
  var arr = [98, 76, 109, 34, 67, 190, 80, 12, 14, 89, 1];
  var n = arr.length;
  quickSort(arr, 0, n - 1);
  console.log(arr); // [1, 12, 14, 34, 67, 76, 80, 89, 98, 109, 190]
}());

(3)选择排序

① 简单选择排序

假设排序表为L[1…n],第i趟排序即从L[1…n]中选择关键字最小的元素与L(i)交换,每一趟排序可以确定一个元素的最终位置,这样经过n-1趟排序就可以使得整个排序表有序。

function selectSort(arr, n) {
  for (var i = 0; i < n - 1; i++) { // 最后一个数字不用排,因为前面已经排好了
    var min = i;
    for (var j = i + 1; j < n; j++) { // 从当前值的下一个数开始比较
      if (arr[j] < arr[min]) min = j; // 找到最小值的位置
    }
    if (min != i) { // 如果当前i位置不是最小值所在的位置,那么就要交换
      var temp = arr[i];
      arr[i] = arr[min];
      arr[min] = temp;
    }
  }
}

(function main() {
  var arr = [98, 76, 109, 34, 67, 190, 80, 12, 14, 89, 1];
  var n = arr.length;
  selectSort(arr, n);
  console.log(arr); // [1, 12, 14, 34, 67, 76, 80, 89, 98, 109, 190]
}());

(4)归并排序

① 2-路归并排序

假定待排序表含有n个记录,则可以看成是n个有序的子表,每个子表长度为1,然后两两归并,得到 ⌈ n / 2 ⌉ \left \lceil n/2 \right \rceil n/2个长度为2或1的有序表:再两两归并,如此重复,直到合并成一个长度为n的有序表为止。

function mergeSort(arr, low, high) {
  if (low < high) { // 因为要不停划分左右子表,如果low = high证明划分完成
    var mid = Math.floor((low + high) / 2); // 划分左右子表
    mergeSort(arr, low, mid); // 对左侧进行递归排序
    mergeSort(arr, mid + 1, high); // 对右侧进行递归排序
    merge(arr, low, mid, high); // 归并
  }
}

function merge(arr, low, mid, high) {
  var brr = [];
  for (var k = low; k <= high; k++) { // 先把arr中的元素全部复制到brr里面
    brr[k] = arr[k];
  }
  for (var i = low, j = mid + 1, k = i; i <= mid&&j <= high; k++) { // 左右子表进行遍历
    if (brr[i] <= brr[j]) { // 每一次都是把左右子表中小的那个数放入arr中
      arr[k] = brr[i++];
    } else {
      arr[k] = brr[j++];
    }
  }
  // 下面这个两个while只会执行其中一个
  // 上面的选择较小值的for循环,是直到左表下标超了mid或者右表下标超了high才会停下来
  // 所以,肯定有一个表已经复制完成,所以只会执行其中一个while循环
  while (i <= mid) arr[k++] = brr[i++]; // 如果第一个表没有检测完,则复制
  while (j <= high) arr[k++] = brr[j++]; // 如果第二个表没有检测完,则复制
}

(function main() {
  var arr = [98, 76, 109, 34, 67, 190, 80, 12, 14, 89, 1];
  var n = arr.length;
  mergeSort(arr, 0, n-1);
  console.log(arr); // [1, 12, 14, 34, 67, 76, 80, 89, 98, 109, 190]
}());

(5)性能总结

在这里插入图片描述

3. 补充一个哈夫曼树

(1)定义

在含有N个带权叶子结点的二叉树中,其中带权路径长度(WPL)最小的二叉树成为哈夫曼树,也成为最优二叉树。

在这里插入图片描述
WPL = 7x2 + 5x2 + 2x2 + 4x2 = 36

(2)构建

给定N个权值分别为w1,w2,…,wn的结点。

  1. 将这N个结点分别作为N棵仅含一个结点的二叉树,构成森林F。
  2. 构造一个新结点,并从F中选取两棵根结点权值最小的树作为新结点的左、右子树,并且将新结点的权值置为左、右子树上根结点的权值之和。
  3. 从F中删除刚才选出的两棵树,同时将新得到的树加入F中。
  4. 重复步骤2和3,直至F中只剩下一棵树为止。

例,有六个点abcdef,权值分别为5,9,12,13,16,45,构建哈夫曼树并且算出WPL

在这里插入图片描述

WPL = 1x45 + 3x(13+12+16) + 4x(5+9) = 224

分解:

  • 先从六个点中找到权值最小的两个点,f(5)和e(9),它们的父节点为14
  • 再从剩下的四个点和父节点14中找出两个权值最小的点,c(12)和b(13),它们的父节点为25
  • 再从剩下的两个点和两个父节点14和25中找出两个权值最小的点,结点14和d(16),它们的父节点为30
  • 再从剩下的一个点和两个父节点25和30中找到两个权值最小的点,结点25和结点30,它们的父节点为55
  • 将剩下的一个点a(45)和结点55组合,它们的父节点为100

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值