数组的算法

Posted 2HAN9LE1

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数组的算法相关的知识,希望对你有一定的参考价值。

数值型数组特征值统计

这里特征值涉及到:平均值,最大值,最小值,总和等

求最大值:将数组第一个元素假设为最大值 int max= arr[0];再然后用写一个判断语句如果数组第一个 元素小于当前比较的元素就把当前比较的元素赋值给max if(max<arr[i])max = arr[i]

求最小值:定义一个变量这个数大于数组里的所有元素例:数组范围是0-99 那么定义的数就为100 然后写一个判断语句if(min>arr[i])min=arr[i]如果min大于arr[i]遍历数组中的元素就把小于的这个数赋值给min ,那么min的值会不断变化例第一次比较100>50 那么min就是50了 下一次比较50>30 那么min下次就变成30了以此类推;

.数组元素的赋值(实际开发中,遇到的场景比较多)

数组的复制

数组的翻转

数组的常见算法2

1.数组的扩容与缩容

2.数组元素的查找

顺序查找

优点

缺点

二分查找

优点

缺点

数组的排序

排序算法的衡量标准

排序的分类

内部排序的具体算法

我们需要关注的几个排序算法

数组在计算机中的执行原理

 在j计算机中程序都是通过内存来执行的,那在java中在编译之后会产生class文件是提取到内存中正在运行的jvm虚拟机来执行的

java为了便于虚拟机执行java程序他将虚拟机内存区域进行了划分大概划分 方法区 栈 堆 本地方法栈 寄存器 重点关注前三个内存区域 接下来讲讲如何配合三块区域来执行java程序的

首先是方法区 存放编译以后字节码文件先加载这里 还有一个区域叫栈方法运行时所进入的内存由于变量是在main方法里面的那也就是说要执行main方法那就是把main方法提取到栈里面来进行执行的,其次是堆 堆中用来存放new出来的东西会在这块堆内存中开辟空间并产生地址

这个空间可以比喻成家里的卫生间 厨房 客厅 每块空间都有自己的功能 首先会将编译好的class文件提取到方法区里接下来就会将方法区里的方法加载到栈内存空间去 然后会执行main方法里的第一行代码 int a = 10 那么就会先定义一个变量然后在栈里面开辟一个空间也就是在main方法里开辟空间 这里面就会存储数据10 接下看来执行下一行代码system.out.println(a),这行代码是直接输出变量a他就会直接打印出变量a,接下来继续执行下面的代码int[]arr = 11,22,33;这时候会先在main方法里开辟arr变量空间一开始这个变量并没有存数据接着他会执行等号右边的代码这个代码其实是在new一个数组对象我们说过只要new的话就在堆内存开辟空间这块区域会等分成三块 第一块区域存11 第二块区域存22 第三块区域存33 这三块区域都会有自己的索引依次分别问0.1.2 并且也会有一个地址接着他会把这个地址赋值给左边的变量arr再由arr来指向右边的数组对象这就是我们数组引用类型的变量他的执行原理再往下执行代码

system.out.println(arr),arr存的是什么啊,他存储的是不是地址啊,接着代码继续执行system.out.println(arr[1]),他在通过arr这个变量可以找到右边的数组对象再通过这个地方申请的索引1定位到第二个位置 他就会把第二个位置值22取出来打印给我们看,接着代码继续往下走arr[0]= 44arr[1]=55arr[2]=66 ,他会通过arr变量的地址找到右边的数组对象再找到第一个位置改成44通过索引1找到第二个索引改成55通过索引2找到第三个位置改成66,接着在执行下边代码system.out.println(arr[0])system.out.println(arr[1])system.out.println(arr[2]),他再次通过arr变量里的地址找到右边的数组对象,索引1找到的是44,索引2找到的是55,索引3找到的是66

KnockoutJS数组比较算法解析

KnockoutJS数组比较算法解析

前端开发中,数组是一种非常有用的数据结构。这篇博客会解释并分析KnockoutJS实现中使用的数据比较算法。

算法的目的

KnockoutJS使用MVVM的思想,view model中的数组元素会对应data model中的数组数据,当用户进行输入或者请求后台时,数组数据会发生变更, 从而带动UI的更新。例如购物车类的页面,用户可以通过点击一些按钮来添加/删除购物车中储存的物品。一个显示购物车中商品详情的列表会根据数组中物品元素的变化实时更新。另一个例子可以是一个展示餐馆等候队列的展示页面,随着客人加入/退出队列,服务器端会不断推送数据到前端页面,实时更新当前最新的队列情况。因此我们需要一个算法,根据更新前的数组数据和更新后的数组数据,计算出一个DOM操作序列,从而使得绑定的DOM元素能根据data model里的数据变化自动更新DOM里的元素。

经典Edit Distance问题

开始正式分析之前,我们先回顾一个经典的算法问题。给定两个字符串,允许“添加”,“删除”或是“替换”字符,如何计算出将一个字符串转换成另一个字符串的操作次数。我们不难发现我们之前讨论的问题和这个经典的问题非常相似,但是又有以下一些不同点:

  • DOM元素并不能很好地支持“替换”的操作。通过浏览器的JavaScript api并不能很高效地将一个DOM元素变换成另一个DOM元素,所以必须通过“添加”和“删除”的组合操作来实现“替换”的等效操作。
  • DOM元素可以支持“移动”操作。尽管原版Edit Distance的并没有提到,但是在浏览器中利用已经存在的DOM元素是一个很合理的做法。
  • 原版问题只要求计算出最小的操作次数,我们的问题里需要计算出一个DOM操作序列。

众所周知,经典Edit Distance的算法使用动态规划实现,需要使用O(m*n)的时间和O(m*n)的空间复杂度(假设两个字符串的长度分别为mn)。

KnockoutJS使用的edit distance算法

KnockoutJS的数组比较算法的第一步是一个变种的edit distance算法,基于具体问题的特殊性进行了一些调整。算法仍然使用动态规划,需要计算出一个2维的edit distance矩阵(叫做M),每个元素对应两个数组的子序列的最小edit distance + 1。比如说,假设两个数组分别叫arr1arr2,矩阵的第i行第j列的值就是arr1[:i]arr2[:j]的最小edit distance + 1。

不难发现,从任意的一个(i,j)配对出发,我们可以有如下的递归关系:

  • arr1[i-1] === arr2[j-1], 我们可以省去一次操作,M[i][j] = M[i-1][j-1]
  • arr1[i-1] !== arryou2[j-1], 这时我们有两种选项,取最小值
    • 删除arr1[i-1],继续比较, 此时M[i][j] = M[i-1][j] + 1
    • 在arr1[i-1]后添加一个等于arr2[j-1]的元素,继续比较,此时M[i][j] = M[i][j-1] + 1

根据这个递推关系可以知道如何初始化好第一行和第一列。算法本身使用循环自下而上实现,可以省去递归带来的额外堆栈开销。

计算edit distance具体代码如下:

// ... preprocess such that arr1 is always the shorter array
var myMin = Math.min,
    myMax = Math.max,
    editDistanceMatrix = [],
    smlIndex, smlIndexMax = smlArray.length,
    bigIndex, bigIndexMax = bigArray.length,
    compareRange = (bigIndexMax - smlIndexMax) || 1,
    maxDistance = smlIndexMax + bigIndexMax + 1,
    thisRow, lastRow,
    bigIndexMaxForRow, bigIndexMinForRow;

for (smlIndex = 0; smlIndex <= smlIndexMax; smlIndex++) {
    lastRow = thisRow;
    editDistanceMatrix.push(thisRow = []);
    bigIndexMaxForRow = myMin(bigIndexMax, smlIndex + compareRange);
    bigIndexMinForRow = myMax(0, smlIndex - 1);
    for (bigIndex = bigIndexMinForRow; bigIndex <= bigIndexMaxForRow; bigIndex++) {
        if (!bigIndex)
            thisRow[bigIndex] = smlIndex + 1;
        else if (!smlIndex)  // Top row - transform empty array into new array via additions
            thisRow[bigIndex] = bigIndex + 1;
        else if (smlArray[smlIndex - 1] === bigArray[bigIndex - 1])
            thisRow[bigIndex] = lastRow[bigIndex - 1];                  // copy value (no edit)
        else {
            var northDistance = lastRow[bigIndex] || maxDistance;       // not in big (deletion)
            var westDistance = thisRow[bigIndex - 1] || maxDistance;    // not in small (addition)
            thisRow[bigIndex] = myMin(northDistance, westDistance) + 1;
        }
    }
}
// editDistanceMatrix now stores the result

算法利用了一个具体问题的特性,那就是头尾交叉的子序列配对不可能出现最优情况。比如说,对于数组abc和efg来说,配对abc和e不可能出现在最优解里。因此算法的第二层循环只需要遍历长数组长度和短数组长度的差值而不是长数组的长度。算法的时间复杂度被缩减到了O(m*(n-m))。因为JavaScript的数组基于object实现,未使用的index不会占用内存,因此空间复杂度也被缩减到了O(m*(n-m))

仔细想想会发现在这个应用场景里,这是一个非常高效的算法。尽管理论最坏复杂度仍然是平方级,但是对于前端应用的场景来说,大部分时间面对的是高频小幅的数据变化。也就是说,在大部分情况下,nm非常接近,因此这个算法在大部分情况下可以达到线性的时间和空间复杂度,相比平方级的复杂度是一个巨大的提升。

在得到edit distance matrix之后获取操作序列就非常简单了,只要从尾部按照之前赋值的规则倒退至第一行或者第一列即可。
计算操作序列具体代码如下:

// ... continue from the edit distance computation
var editScript = [], meMinusOne, notInSml = [], notInBig = [];
for (smlIndex = smlIndexMax, bigIndex = bigIndexMax; smlIndex || bigIndex;) {
    meMinusOne = editDistanceMatrix[smlIndex][bigIndex] - 1;
    if (bigIndex && meMinusOne === editDistanceMatrix[smlIndex][bigIndex-1]) {
        notInSml.push(editScript[editScript.length] = {     // added
            'status': statusNotInSml,
            'value': bigArray[--bigIndex],
            'index': bigIndex });
    } else if (smlIndex && meMinusOne === editDistanceMatrix[smlIndex - 1][bigIndex]) {
        notInBig.push(editScript[editScript.length] = {     // deleted
            'status': statusNotInBig,
            'value': smlArray[--smlIndex],
            'index': smlIndex });
    } else {
        --bigIndex;
        --smlIndex;
        if (!options['sparse']) {
            editScript.push({
                'status': "retained",
                'value': bigArray[bigIndex] });
        }
    }
}
// editScript has the (reversed) sequence of actions

元素移动优化

如之前提到的,利用已有的重复元素可以减少不必要的DOM操作,具体实现方法非常简单,就是遍历所有的“添加”,“删除”操作,检查是否有相同的元素同时被添加和删除了。这个过程最坏情况下需要O(m*n)的时间复杂度,破环了之前的优化,因此算法提供一个可选的参数,在连续10*m个配对都没有发现可移动元素的情况下直接退出算法,从而保证整个算法的时间复杂度接近线性。

检查可移动元素的具体代码如下:

// left is all the operations of "Add"
// right is all the operations of "Delete
if (left.length && right.length) {
    var failedCompares, l, r, leftItem, rightItem;
    for (failedCompares = l = 0; (!limitFailedCompares || failedCompares < limitFailedCompares) && (leftItem = left[l]); ++l) {
        for (r = 0; rightItem = right[r]; ++r) {
            if (leftItem['value'] === rightItem['value']) {
                leftItem['moved'] = rightItem['index'];
                rightItem['moved'] = leftItem['index'];
                right.splice(r, 1);         // This item is marked as moved; so remove it from right list
                failedCompares = r = 0;     // Reset failed compares count because we're checking for consecutive failures
                break;
            }
        }
        failedCompares += r;
    }
}
// Operations that can be optimized to "Move" will be marked with the "moved" property

完整的相关代码可以在这里找到
knockout/src/binding/editDetection/compareArrays.js

以上是关于数组的算法的主要内容,如果未能解决你的问题,请参考以下文章

算法刷题-数组排序(图算法算法高阶)螺旋矩阵(数组矩阵)分发糖果(贪心数组)

前端算法入门:刷算法题常用的 JS 基础扫盲

数组和数组排序算法

算法常见数组搜索算法

请教一个关于二维数组的算法

数组的简单命令,以及排序算法