本文实例讲述了javascript常用算法。分享给大家供大家参考,具体如下:
入门级算法-
线性查找
-时间复杂度O(n)--相当于算法界中的HelloWorld二分查找
(又称折半查找) - 适用于已排好序的线性结构 - 时间复杂度O(logN)冒泡排序
-- 时间复杂度O(n^2)选择排序
-- 时间复杂度O(n^2)插入排序
-- 时间复杂度O(n^2)字符串反转
-- 时间复杂度O(logN)关于
稳定性排序
的一个结论:基于比较的简单排序算法,即时间复杂度为O(N^2)的排序算法,通常可认为均是稳定排序 其它先进的排序算法,比如归并排序、堆排序、桶排序之类(通常这类算法的时间复杂度可优化为n*LogN),通常可认为均是不稳定排序
单链表
实现 "); } //节点类 var Node = function (v) { this.data = v; //节点值 this.next = null; //后继节点 } //单链表 var SingleLink = function () { this.head = new Node(null); //约定头节点仅占位,不存值 //插入节点 this.insert = function (v) { var p = this.head; while (p.next != null) { p = p.next; } p.next = new Node(v); } //删除指定位置的节点 this.removeAt = function (n) { if (n <= 0) { return; } var preNode = this.getNodeByIndex(n - 1); preNode.next = preNode.next.next; } //取第N个位置的节点(约定头节点为第0个位置) //N大于链表元素个数时,返回最后一个元素 this.getNodeByIndex = function (n) { var p = this.head; var i = 0; while (p.next != null && i < n) { p = p.next; i++; } return p; } //查询值为V的节点, //如果链表中有多个相同值的节点, //返回第一个找到的 this.getNodeByValue = function (v) { var p = this.head; while (p.next != null) { p = p.next; if (p.data == v) { return p; } } return null; } //打印输出所有节点 this.print = function () { var p = this.head; while (p.next != null) { p = p.next; print(p.data + " "); } println(""); } } //测试单链表L中是否有重复元素 function hasSameValueNode(singleLink) { var i = singleLink.head; while (i.next != null) { i = i.next; var j = i; while (j.next != null) { j = j.next; if (i.data == j.data) { return true; } } } return false; } //单链表元素反转 function reverseSingleLink(singleLink) { var arr = new Array(); var p = singleLink.head; //先跑一遍,把所有节点放入数组 while (p.next != null) { p = p.next; arr.push(p.data); } var newLink = new SingleLink(); //再从后向前遍历数组,加入新链表 for (var i = arr.length - 1; i >= 0; i--) { newLink.insert(arr[i]); } return newLink; } var linkTest = new SingleLink(); linkTest.insert('A'); linkTest.insert('B'); linkTest.insert('C'); linkTest.insert('D'); linkTest.print();//A B C D var newLink = reverseSingleLink(linkTest); newLink.print();//D C B A @H_403_7@关于
邻接矩阵、邻接表
的选择:邻接矩阵、邻接表都是图的基本存储方式,
稀松图
情况下(即边远小于顶点情况下),用邻接表存储比较适合(相对矩阵N*N而言,邻接表只存储有值的边、顶点,不存储空值,存储效率更高)稠密图
情况下(即边远大地顶点情况下),用邻接矩阵存储比较适合(数据较多的情况下,要对较做遍历,如果用链表存储,要经常跳来跳去,效率较低)堆:
几乎完全的二叉树
:除了最右边位置上的一个或几个叶子可能缺少的二叉树。在物理存储上,可以用数组来存储,如果A[j]的顶点有左、右子节点,则左节点为A[2j]、右节点为A[2j+1],A[j]的父顶点存储在A[j/2]中堆:
本身是一颗几乎完全的二叉树,而且父节点的值不小于子节点的值。应用场景:优先队列,寻找最大或次最大值;以及把一个新元素插入优先队列。注:以下所有讨论的堆,约定索引0处的元素仅占位,有效元素从下标1开始
根据堆的定义,可以用以下代码测试一个数组是否为堆:
节点向上调整siftUp
某些情况下,如果堆中的某个元素值改变后(比如 10,8,9,7 变成 10,20 后,20需要向上调整 ),不再满足堆的定义,需要向上调整时,可以用以下代码实现
节点向下调整siftDown (既然有向上调整,自然也有向下调整)
向堆中添加新元素
从堆中删除元素
堆排序
:这是一种思路非常巧妙的排序算法,精华在于充分利用了“堆”这种数据结构本身的特点(首元素必然最大),而且每个元素的上移、下调,时间复试度又比较低,仅为O(logN),空间上,也无需借助额外的存储空间,仅在数组自身内部交换元素即可。
思路:
1、先将首元素(即最大元素)与最末尾的元素对调---目的在于,把最大值沉底,下一轮重就不再管它了 2、经过1后,剩下的元素通常已经不再是一个堆了。这时,只要把新的首元素用siftDown下调,调整完以后,新的最大值元素自然又上升到了首元素的位置 3、反复1、2,大的元素逐一沉底,最后整个数组就有序了。 时间复杂度分析:创建堆需要O(n)的代价,每次siftDown代价为O(logN),最多调整n-1个元素,所以总代价为 O(N) + (N-1)O(logN),最终时间复杂度为O(NLogN)
关于建堆,如果明白其中的原理后,也可以逆向思路,反过来做
不相交集合查找、合并
归纳法
:先来看二个排序的递归实现
递归的程序通常易于理解,代码也容易实现,再来看二个小例子:
从数组中,找出最大值
有一个已经升序排序好的数组,检查数组中是否存在二个数,它们的和正好为x ?
递归程序虽然思路清晰,但通常效率不高,一般来讲,递归实现,都可以改写成非递归实现,上面的代码也可以写成:
递归并不总代表低效率,有些场景中,递归的效率反而更高,比如计算x的m次幂,常规算法,需要m次乘法运算,下面的算法,却将时间复杂度降到了O(logn)
当然,这其中并不光是递归的功劳,其效率的改进 主要依赖于一个数学常识: x^m = [x^(m/2)]^2,关于这个问题,还有一个思路很独特的非递归解法,巧妙的利用了二进制的特点
再来看看经典的
多项式求值
问题:给定一串实数An,An-1,...,A1,A0 和一个实数X,计算多项式Pn(x)的值
著名的Horner公式:
已经如何计算:
显然有:
@H_301_190@
这样只需要 N次乘法+N次加法
多数问题
:一个元素个数为n的数组,希望快速找出其中大于出现次数>n/2的元素(该元素也称为多数元素)。通常可用于选票系统,快速判定某个候选人的票数是否过半。最优算法如下:
以上算法基于这样一个结论:在原序列中去除两个不同的元素后,那么在原序列中的多数元素在新序列中还是多数元素
证明如下:
如果原序列的元素个数为n,多数元素出现的次数为x,则 x/n > 1/2 去掉二个不同的元素后, a)如果去掉的元素中不包括多数元素,则新序列中 ,原先的多数元素个数/新序列元素总数 = x/(n-2) ,因为x/n > 1/2 ,所以 x/(n-2) 也必然>1/2 b)如果去掉的元素中包含多数元素,则新序列中 ,原先的多数元素个数/新序列元素总数 = (x-1)/(n-2) ,因为x/n > 1/2 =》 x>n/2 代入 (x-1)/(n-2) 中, 有 (x-1)/(n-2) > (n/2 -1)/(n-2) = 2(n-2)/(n-2) = 1/2
下一个问题:
全排列
"); } //全排列算法 function perm(P,m) { var n = P.length - 1; if (m == n) { //完成一个新排列时,输出 println(P); return; } for (var j = m; j <= n; j++) { //将起始元素与后面的每个元素交换 swap(P,m); //在前m个元素已经排好的基础上 //再加一个元素进行新排列 perm(P,m + 1); //把j与m换回来,恢复递归调用前的“现场", //否则因为递归调用前,swap已经将原顺序破坏了, //导致后面生成排序时,可能生成重复 swap(P,m); } } perm([1,3],0); //1,3 //1,2 //2,3 //2,1 //3,2 @H_403_7@分治法
:要点:将问题划分成二个子问题时,尽量让子问题的规模大致相等。这样才能最大程度的体现一分为二,将问题规模以对数折半缩小的优势。
"); } //数组中i,j位置的元素交换(辅助函数) function swap(A,j) { var t = A[i]; A[i] = A[j]; A[j] = t; } //寻找数组A中的最大、最小值(分治法实现) function findMinMaxDiv(A,low,high) { //最小规模子问题的解 if (high - low == 1) { if (A[low] < A[high]) { return [A[low],A[high]]; } else { return [A[high],A[low]]; } } var mid = Math.floor((low + high) / 2); //在前一半元素中寻找子问题的解 var r1 = findMinMaxDiv(A,mid); //在后一半元素中寻找子问题的解 var r2 = findMinMaxDiv(A,mid + 1,high); //把二部分的解合并 var x = r1[0] > r2[0] ? r2[0] : r1[0]; var y = r1[1] > r2[1] ? r1[1] : r2[1]; return [x,y]; } var r = findMinMaxDiv([1,8],7); println(r); //1,8 //二分搜索(分治法实现) //输入:A为已按非降序排列的数组 //x 为要搜索的值 //low,high搜索的起、止索引范围 //返回:如果找到,返回下标,否则返回-1 function binarySearchDiv(A,x,high) { if (low > high) { return -1; } var mid = Math.floor((low + high) / 2); if (x == A[mid]) { return mid; } else if (x < A[mid]) { return binarySearchDiv(A,mid - 1); } else { return binarySearchDiv(A,high); } } var f = binarySearchDiv([1,7],6); println(f); //3 //将数组A,以low位置的元素为界,划分为前后二半 //n为待处理的索引范围上限 function split(A,n) { if (n >= A.length - 1) { n = A.length - 1; } var i = low; var x = A[low]; //二个指针一前一后“跟随”, //最前面的指针发现有元素比分界元素小时,换到前半部 //后面的指针再紧跟上,“夫唱妇随”一路到头 for (var j = low + 1; j <= n; j++) { if (A[j] <= x) { i++; if (i != j) { swap(A,j); } } } //经过上面的折腾后,除low元素外,其它的元素均以就位 //最后需要把low与最后一个比low位置小的元素交换, //以便把low放在分水岭位置上 swap(A,i); return [A,i]; } var A = [5,3]; var b = split(A,A.length - 1); println(b[0]); //3,6 //快速排序 function quickSort(A,high) { var w = high; if (low < high) { var t = split(A,w); //分治思路,先分成二半 w = t[1]; //在前一半求解 quickSort(A,w - 1); //在后一半求解 quickSort(A,w + 1,high); } } var A = [5,3]; quickSort(A,A.length - 1); println(A); //3,7 @H_403_7@
split算法的思想应用
:设A[1..n]是一个整数集,给出一算法重排数组A中元素,使得所有的负整数放到所有非负整数的左边,你的算法的运行时间应当为Θ(n)
希望本文所述对大家JavaScript程序设计有所帮助。