js常用排序算法(冒泡,插入,快速,选择)_唐宋丶元明清的博客-CSDN博客
快速排序法
1..选择排序大致的思路是找到数据结构中的最小值并 将其放置在第一位,接着找到第二小的值并将其放在第二位 2.可以理解成'选择排序'将整个排序分成两个区间,分别是'排序区间'和'未排序区间',选择排序每次会从'未排序区间'中找到 最小的元素,将其放到已排序区间的末尾。 3.对引用图进行说明一下,引用第一个图中的第二组数据说明 '1 5 6 3 2 4',现在的1就是排序区间,'5 6 3 2 4' 就是为排序区间,这里要说图虽然是 5 和 2 直接交换,但实际末尾的'4' 也是比较过了,只是图上的效果感觉4没有参与本次运算
“等价替换”,通过数组循环来求出数组中最小值
思路:默认‘某个值为最小值,但实际这个值可能真是最小值,也可能不是',我们用这个值去和其他值比较,然后如果有比这个值还小的值,那这个值就成为我们默认的最小值
// 等价替换
function getArrayMin(array, compareFn = defaultCompare){
const {length} = array
let min = array[0]
for(let i = 1;i < length; i++){
if(compareFn(min, array[i]) === Compare.BIGGER_THAN){
min = array[i]
}
}
return min
}
console.log(array)
console.log(Math.min(...array))
console.log(getArrayMin(array))
// 不懂
1.这里有个思维转换,当我们求一个数组中最小值和最大值的时候,我们用等价替换的方法,只是 循环了一次,
问题来了当我们要把数组中所有的都进行排序那单单的一次循环肯定不够的,这时候就是双层for 循环
2.这里还要注意的是'选择排序'的概念,我们其实会将整个数组分成两个区域,'排序区间'和'未排序区间',其中'排序区间'
是在数组前半部分,因此已经排序过的地方肯定是不需要在进行排序因此第二层的循环条件也变成了'let j = i; j < length; j++'
function selectionSort(array,compareFn = defaultCompare){
const {length} = array
let indexMin
for(let i = 0; i < length - 1; i++){
if(compareFn(array[indexMin],array[j]) === Compare.BIGGER_THAN){
indexMin = j
}
}
if(i !== indexMin){
swap(array,i,indexMin)
}
return array
}
console.log(selectionSort(array))
插入排序
1.引用书里的话来说,插入排序每次排一个数组项,以此方式构建最后的排序数组。假定第一项已经排序了。接着,它和第二项进行比较--第二项是应该待在原位还是插到第一项之前呢?这样,头两项就已正确,排序接着和第三项比较(它是该插入到第一,第二还是第三位置呢)以此类推
2.通俗理解'插入排序'和‘选择排序'在整理思路方面差不多,首先'插入排序'也是将整个排序分成两个区间,分别是'排序区间'和‘未排序区间’,每次会从‘未排序区间’中取值去以排序区间中比较,比较后将这个值插入到‘以排序区间’
3.‘插入排序’和‘选择排序’做个比较理解,‘插入’是从‘未排序区间’取值在‘以排序区间去比较’,‘选择‘是从’未排序区间‘依次找到最小值放到’以排序区间‘
// 生成随机数数组
function randomArray(max, min, len){
let randomNum = 0
const array = []
for(let i = 0; i < len; i++){
randomNum = Math.floor(Math.random() * (max - min + 1)) + min)
array.push(randomNum)
}
return array
}
const Compare = {
LESS_THAN: -1,
BIGGER_THAN: 1,
EQUALS: 0
}
function defaultCompare(a, b){
if(a === b){
return Compare.EQUALS
}
return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN
}
这里默认将第一个元素作为'已排序'区间中的内容,这样方便后续逻辑
通过动态图来理解下面插入算法中while逻辑,首先是把整个数组分成两个区域'已排序区域'一个是'为排序区域',第一层for是从为排序区域取出一个,‘已排序区域’就是从当前这个值往后都是以排序区域,因此while的判断循环j是从取点位置开始的,注意动图插入的那个动作,如果你比我小我就把你往后移动,如果你比我大舅到大的位置,整个while就结束了,这个值就到了j的位置
fucntion insertionSort(array,compareFn = defaultCompare){
const {length} = array
let temp
for(let i = 1; i < length; i++){
let j = i;
temp = array[i]
while(j > 0 && compareFn(array[j-1],temp) === Compare.BIGGER_THAN){
array[j] = array[j - 1]
j--
}
array[j] = temp
}
return array
}
insertionSort(array)
console.log(array)
归并排序
1.‘归并排序’:将原始数组切分成较小的数组,直到每个小数组只有一个位置,接着将小数组归并成较大的数组,直到最后一个排序完毕的大数组
2.如图和定义分析首先需要两步(因此我们需要两个方法):
2.1第一个方法是分解需要将大数组不停的切割,直到每个项变成单独一个,这个过程需要一个递归
2.2第二个方法需要合并在比较完成后,将每个小块一次比较最后形成一个大数组
3.上面用到的思想叫‘分治思想’。将一个大问题分解成小的子问题来解决。小的子问题解决了,大问题也解决了
function randomArray(max, min, len){
let randomNum = 0
const array = []
for(let i = 0; i < len; i++){
randomNum = Math.floor(Math.random() * (max - min + 1)) + min)
array.push(randomNum)
}
return array
}
const Compare = {
LESS_THAN: -1,
BIGGER_THAN: 1,
EQUALS: 0
}
function defaultCompare(a, b){
if(a === b){
return Compare.EQUALS
}
return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN
}
1.跟上面的分析一样需要两个方法第一个方法是分,也就是将数组分成最小单个为一组数组方法'mergeSort',第二个就是将这些细分维度的数组依次比较合并'merge'方法,这里要注意的是'merge'方法里面有个暂存新排序的数组
2.‘ mergeSort‘方法比较好理解就是一个递归过程,这个过程需要将数组项不停拆分成左右结构,递归的过程之前章节有分析过,要有一个终止条件这个条件就是'归‘,这个方法中的’归‘就是将数组已经拆分成最小结构单独项对应代码中’array.length > 1'判断条件
3.‘merge'这个合并方法比较复杂这里拆分了三个结构依次对应{1},{2},{3}
3.1 '{1},{2}'-- 数组会被拆分,这里拿出某一刻的拆分,来对这一部分的代码做解释,当左侧拆分为'[3,6]',右侧拆分为'[4,5,8]',这时候需要逻辑是右侧数组中的内容一次和左侧数组中的内容比较,比较完后会放到‘merge'方法中的一个暂存数组,形成数据状态为[3,4,5,6].这里要注意的是{2},
首先明确 'i'和'j' 不会同时自增,他们是根据比较状态来决定那个自增,这样就可以形成依次比较
3.2.'{3}' -- 当上面将整体数组排序后,将最后剩余值添加到数组末尾
4.这里来个总结版本:归并首先采用的是'分治思想',这类思想很像递归,把大问题拆成小问题,小问题
解决了,大问题最后也就跟着解决了,简单的说就是'逐一击破'
4.1.'逐一击破'我们要做的就是将数组不停切割,切割的原则是数组长度的二分之一,这种切割就会产生
两个数组,我们要做是在切割最小单位及左右数组里面都只有一个值时候开始我们的比较
4.2.'在选排序' 和 '插入排序'的时候这两者用的是,将数组分成两个区域'已排序'和'为排序'两个区域,
'归并排序' 我们将不采用这种结构划分,我会用另外一个数组来存储这些排序后的数据
4.3.上两步我们做好了'拆分'和'比较后值应该存放的位置',现在要解决如何比较左右两个数组值举个例子,
现在有个数组'[6,7,8,1,3,2,5]',经过拆分后他将呈现效果是:
[ 7 ] [ 8 ]
[ 6 ] [ 7, 8 ]
[ 1 ] [ 3 ]
[ 2 ] [ 5 ]
[ 1, 3 ] [ 2, 5 ]
[ 6, 7, 8 ] [ 1, 3, 2, 5 ]
现在要解决的是两个数组之间值的比较,这里采用指针的解决思路,分别有两个指针,最初位置分别为两个
相互比较数组其实位置
4.4.较两个指针所指向的元素,选择相对小的元素放入到合并空间就是我们在4.2位置创建数组,
并移动指针到下一位置,一直重复这个过程,直到某一指针达到序列尾,将另一序列剩下的所有
元素直接复制到合并序列尾
5.整个归并解决思路:
5.1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
5.2.设定两个指针,最初位置分别为两个已经排序序列的起始位置;
5.3.比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
5.4.重复步骤 3 直到某一指针达到序列尾;
5.5.将另一序列剩下的所有元素直接复制到合并序列尾。
//1.将数组进行切割变成只用单独一项
function mergeSort(array,compareFn = defaultCompare){
// 如果数组只有一项就不用进行比较了
// 这是一个递归方法因此 这判断条件相当于'归' 结束递归过程
if(array.length>1){
const {length} = array
const middle = Math.floor(length / 2)
const left = mergeSort(array.slice(0,middle),compareFn)
const right = mergeSort(array.slice(middle,length),compareFn)
// console.log(left,right)
// 第二步需要合并将这些单个项进行合并比较
array= merge(left,right,compareFn)
}
// console.log(array,222)
return array
}
function merge(left, right, compareFn) {
let i = 0;
let j = 0;
const result = [];
console.log(left,'left');
console.log(right,'right');
while (i < left.length && j < right.length) { // {1}
result.push(
compareFn(left[i], right[j]) === Compare.LESS_THAN ? left[i++] : right[j++] // {2}
);
}
console.log(result,2);
const a = result.concat(i < left.length ? left.slice(i) : right.slice(j)); // {3}
console.log(a,2222);
return a
}
// console.log(array)
console.log(mergeSort([3,6,4,5,8]))
快速排序
1.快速排序基本思想:通过一趟排序将要排序的数据分隔成独立的‘两部分’,其中一部分的所有数据都比另外一部分所有数据都要‘小’,然后再按此方法对这两部分数据进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
2.根据上面的概念最重要的一点是要让一组数据比另一组数据都‘小’,为了做到这个,可以采用的思想方式是,找到这一组数据中的任意一个值作为‘基准’在快速排序汇总我们叫做‘主元‘,这个’主元‘就会将数据分隔成左右两个部分,这两个部分数据依次和’主元‘比较,最后比主元大的都在一侧,小的在一侧
搜索算法
1.在实际开发中,经常我们会在数组中找寻其中一个值的位置,在不借助js护足自带api,'findIndex'
这节讨论这些自行实现的思路
1.顺序或线性搜索是最基本的搜索算法。它的机制是,将每一个数据结构中的元素和我们要找的元素做比较。顺序搜索是最低效的一种搜索算法
function defaultEquals(a, b){
return a === b
}
function sequentialSearch(array, value, equalsFn = defaultEquals){
for(let i = 0; i < array.length; i++){
if(equalsFn(value, array[i]){
return i
}
}
return -1
}
const a = sequentialSearch([6,5,7],5)
const b = sequentialSearch([6,5,7],1)
console.log(a, b) // 1 -1
二分搜索
1.二分查找针对的是一个有序的数据集合,查找思想有点类似分治思想。每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为0
二分查找针对的是一个有序的数据集合,下面的案例我将快排的引用注释掉了,采用了血丝的一个案例方式,当然也可以直接用数组自带的sort方法。
const Compare = {
LESS_THAN: -1,
BIGGER_THAN: 1,
EQUALS: 0
}
function lesserOrEquals(a, b, compareFn){
const comp = compareFn(a, b)
return comp === Compare.LESS_THAN || comp === Compare.EQUALS
}
function defaultCompare(a, b){
if(a === b){
return Compare.EQUALS
}
return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN
}
// 二分搜索需要先排序
// 查找的数组 要查找的值
function binarySearch(array, value){
const sortedArray = [1,2,3,6,8,24]
let low = 0
let height = sortedArray.length - 1
/**
* 如果排序是从小到大
* 如果查询的值大于中间项,说明要查找的值在右侧,那我们最小指针就变成 mid +1
* 如果查询的值小于中间项,说明要查找的值在左侧,那我们最大指针就变成 mid -1
* 如果相等是要找值
位置
*/
while(lesserOrEquals(low,height,defaultCompare)){ // 循环结束的条件当最小值和最大值错位或者相等时候
const mid = Math.floor((low + height)/2) // 先找到中间位置
const element = sortedArray[mid] // 取出对应的中间值
if (defaultCompare(element, value) === Compare.LESS_THAN) { // {7}
low = mid + 1; // {8}
} else if (defaultCompare(element, value) === Compare.BIGGER_THAN) { // {9}
height = mid - 1; // {10}
} else {
return mid; // {11}
}
}
return -1
}
console.log( binarySearch(1,8))
数组,splice的增加和删除数组中数据的方法
let arr = [10,20,30,40];
// splice(n,m)从数组索引n开始,删除m个元素,把删除的部分以新数组包裹起来返回,原数组发生改变。
// let res = arr.splice(1,1)
// => res [20]
// => arr [10,30,40]
// splice(n,0,x) 从索引n开始,不删除,把x添加到索引n的前面
let res = arr.splice(1,0,'珠峰培训');
console.log(res); // []
console.log(arr); // [10,'珠峰培训',20,30,40]
数组:sort
let ary = [3,6,1,7,0];
ary.sort();
console.log(ary); // [0,1,3,6,7]
let ary = [12,45,28,35,4];
// 局限性:不传递参数的情况下只能处理10以内的数组排序
// ary.sort();
// console.log(ary); // => [12,28,35,4,45]
ary.sort(function(a,b){
return a-b;
})
console.log(ary); // => [4,12,28,35,45]
冒泡排序:泡泡就是小的在底下,大的在上面
/**
*冒泡排序的思想:
* 让数组中的当前项和后一项进行比较,如果当前项比后一项大,则两项交换位置(让大的靠后)即可
**/
let ary = [12,8,24,16,1];
/**
*bubble:实现冒泡排序
* @params
* arr [ARRAY] 需要排序的数组
* @return
* [ARRAY] 排序后的新数组
* by xingxing on 2021/11/14
**/
function bubble(arr){
let temp = null;
// 外层循环I控制比较的轮数
for(let i = 0; i < arr.length-1;i++){
i = 0 一个都没放呢
i = 1 已经放了一个了
i = 2 已经放了两个了
// 里层循环控制每一轮比较的次数J
for(let j = 0; j < arr.length - 1 -i;j++){
if(arr[j] > arr[j+1]){
// 当前项大于后一项
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
return arr;
}
arr = bubble(arr);
console.log(arr);
冒泡排序练习题
//从小到大(冒泡排序)
let arr = [5, 4, 3, 2, 9, 2, 3, 4, 2, 1, 6, 23, 11, 52, 66, 87]; //给定一个数组
for (let i = 0; i <= arr.length; i++) { //外层循环遍历数组里面每一个数
for (let j = i; j < arr.length; j++) { //内层循环数组的下一个数
if (arr[i] > arr[j]) { //通过比较大小 前一个数大于后一个数交换
let max = arr[i]; //记录较大的数
arr[i] = arr[j]; //把小数给arr[i]
arr[j] = max; //把大数给arr[j] 这样是实现前后相邻俩位数的交换
}
}
}
console.log(arr); //[1, 2, 2, 2, 3, 3, 4, 4, 5, 6, 9, 11, 23, 52, 66, 87]
//从大到小(冒泡排序)
let arr = [5, 4, 3, 2, 9, 2, 3, 4, 2, 1, 6, 23, 11, 52, 66, 87];
for (let i = 0; i <= arr.length; i++) { //外层循环遍历数组里面每一个数
for (let j = i; j < arr.length; j++) { //内层循环数组的下一个数
if (arr[i] < arr[j]) { //通过比较大小 前一个数小于后一个数交换
let min = arr[i]; //记录较大的数
arr[i] = arr[j]; //把大数给arr[i]
arr[j] = min; //把小数给arr[j] 这样是实现前后相邻俩位数的交换
}
}
}
console.log(arr); //[87, 66, 52, 23, 11, 9, 6, 5, 4, 4, 3, 3, 2, 2, 2, 1]
https://www.jb51.net/article/100470.htm
快速排序
quickSort = arr => {
let l = arr.length
let leftArr = []
let rightArr = []
let a = arr[0]
if( l <= 1) {
return arr
}
for(let i = 1; i < l; i++) {
if(arr[i] > a) {
rightArr.push(arr[i]);
}
else{
leftArr.push(arr[i]);
}
}
return [].concat(quickSort(leftArr),[a],quickSort(rightArr))
}
console.log('结果:', quickSort([5,1,6,7]))
全排列
func = arr => {
let len = arr.length
let res = [] // 所有排列结果
/**
* 【全排列算法】
* 说明:arrange用来对arr中的元素进行排列组合,将排列好的各个结果存在新数组中
* @param tempArr:排列好的元素
* @param leftArr:待排列元素
*/
let arrange = (tempArr, leftArr) => {
if (tempArr.length === len) { // 这里就是递归结束的地方
res.push(tempArr.join('')) // 得到全排列的每个元素都是字符串
} else {
leftArr.forEach((item, index) => {
let temp = [].concat(leftArr)
temp.splice(index, 1)
// 此时,第一个参数是当前分离出的元素所在数组;第二个参数temp是传入的leftArr去掉第一个后的结果
arrange(tempArr.concat(item), temp) // 这里使用了递归
})
}
}
arrange([], arr)
return res
}
console.log('结果:', func(['A', 'B', 'C', 'D']))
js 实现队列
/** 队列思想(FIFO) 先进先出
* enqueue(element):向队列尾部添加一个(或多个)新的项;
* dequeue():移除队列的第一(即排在队列最前面的)项,并返回被移除的元素;
* front():返回队列中的第一个元素——最先被添加,也将是最先被移除的元素。
队列不做任何变动(不移除元素,只返回元素信息与Stack类的peek方法非常类似);
* isEmpty():如果队列中不包含任何元素,返回true,否则返回false;
* size():返回队列包含的元素个数,与数组的length属性类似;
* toString():将队列中的内容,转成字符串形式
*/
function Queue() {
this.items = []
// enqueue():将元素加入到队列中
Queue.prototype.enqueue = element => {
this.items.push(element)
}
// dequeue():从队列中删除前端元素
Queue.prototype.dequeue = () => {
return this.items.shift()
}
// front():查看前端的元素
Queue.prototype.front = () => {
return this.items[0]
}
// isEmpty:查看队列是否为空
Queue.prototype.isEmpty = () => {
return this.items.length === 0
}
// size():查看队列中元素的个数
Queue.prototype.size = () => {
return this.items.length
}
// toString():将队列中元素以字符串形式输出
Queue.prototype.toString = () => {
let resultString = ''
for (let i of this.items){
resultString += i + ' '
}
return resultString
}
}
// 创建队列
let queue = new Queue()
// 加入队列
queue.enqueue('a')
queue.enqueue('b')
queue.enqueue('c')
console.log(queue)
// 从队列中删除
queue.dequeue()
console.log(queue)
// 查看前端元素
console.log(queue.front())
// 判断是否为空
console.log(queue.isEmpty())
// 查看 size
console.log(queue.size())
// 字符串形式输出
console.log(queue.toString())
js 实现栈
/** 栈的思想: 先进后出,后进先出
* enStack(element):添加一个新元素到栈顶位置;
* deStack():移除栈顶的元素,同时返回被移除的元素;
* peek():返回栈顶的元素,不对栈做任何修改(该方法不会移除栈顶的元素,仅仅返回它);
* isEmpty():如果栈里没有任何元素就返回true,否则返回false;
* size():返回栈里的元素个数。这个方法和数组的length属性类似;
* toString():将栈结构的内容以字符串的形式返回。
*
*/
function Stack() {
this.items = []
// 压栈
Stack.prototype.enStack = element => {
this.items.push(element)
}
// 出栈
Stack.prototype.deStack = () => {
return this.items.pop()
}
// 查看栈顶
Stack.prototype.peek = () => {
return this.items[this.items.length-1]
}
// 判断是否为空
Stack.prototype.isEmpty = () => {
return this.items.length === 0
}
// 查看栈中元素个数
Stack.prototype.size = () => {
return this.items.length
}
// 以字符串的形式输出
Stack.prototype.toString = () => {
let resultString = ''
for(let i of this.items) {
resultString += i + ' '
}
return resultString
}
}
// 创建栈
let stack = new Stack()
// 压栈
stack.enStack('a')
stack.enStack('b')
stack.enStack(3)
console.log(stack)
// 出栈
console.log(stack.deStack())
// 查看栈顶
console.log(stack.peek())
// 判断是否为空
console.log(stack.isEmpty())
// 查看栈中元素个数
console.log(stack.size())
// 以字符串的形式输出
console.log(stack.toString())