javascript常用经典算法实例详解

javascript常用经典算法实例详解

这篇文章主要介绍了javascript常用算法,结合实例形式较为详细的分析总结了JavaScript中常见的各种排序算法以及堆、栈、链表等数据结构的相关实现与使用技巧,需要的朋友可以参考下

本文实例讲述了javascript常用算法。分享给大家供大家参考,具体如下:

入门级算法-线性查找-时间复杂度O(n)--相当于算法界中的HelloWorld

?


1

2

3

4

5

6

7

8

9

10

//线性搜索(入门HelloWorld)

//A为数组,x为要搜索的值

function linearSearch(A, x) {

  for (var i = 0; i < A.length; i++) {

    if (A[i] == x) {

      return i;

    }

  }

  return -1;

}

二分查找(又称折半查找) - 适用于已排好序的线性结构 - 时间复杂度O(logN)

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

//二分搜索

//A为已按"升序排列"的数组,x为要查询的元素

//返回目标元素的下标

function binarySearch(A, x) {

  var low = 0, high = A.length - 1;

  while (low <= high) {

    var mid = Math.floor((low + high) / 2); //下取整   

    if (x == A[mid]) {

      return mid;

    }

    if (x < A[mid]) {

      high = mid - 1;

    }

    else {

      low = mid + 1;

    }

  }

  return -1;

}

冒泡排序 -- 时间复杂度O(n^2)

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

//冒泡排序

function bubbleSort(A) {

  for (var i = 0; i < A.length; i++) {

    var sorted = true;

  //注意:内循环是倒着来的

    for (var j = A.length - 1; j > i; j--) {

      if (A[j] < A[j - 1]) {

        swap(A, j, j - 1);

        sorted = false;

      }

    }

    if (sorted) {

      return;

    }

  }

}

选择排序 -- 时间复杂度O(n^2)

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

//选择排序

//思路:找到最小值的下标记下来,再交换

function selectionSort(A) {

  for (var i = 0; i < A.length - 1; i++) {

    var k = i;

    for (var j = i + 1; j < A.length; j++) {

      if (A[j] < A[k]) {

        k = j;

      }

    }

    if (k != i) {

      var t = A[k];

      A[k] = A[i];

      A[i] = t;

      println(A);

    }

  }

  return A;

}

插入排序 -- 时间复杂度O(n^2)

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

//插入排序

//假定当前元素之前的元素已经排好序,先把自己的位置空出来,

//然后前面比自己大的元素依次向后移,直到空出一个"坑",

//然后把目标元素插入"坑"中

function insertSort(A) {

  for (var i = 1; i < A.length; i++) {

    var x = A[i];

    for (var j = i - 1; j >= 0 && A[j] > x; j--) {

      A[j + 1] = A[j];

    }

    if (A[j + 1] != x) {

      A[j + 1] = x;

      println(A);

    }

  }

  return A;

}

字符串反转 -- 时间复杂度O(logN)

?


1

2

3

4

5

6

7

8

9

10

11

12

13

//字符串反转(比如:ABC -> CBA)

function inverse(s) {

  var arr = s.split(‘‘);

  var i = 0, j = arr.length - 1;

  while (i < j) {

    var t = arr[i];

    arr[i] = arr[j];

    arr[j] = t;

    i++;

    j--;

  }

  return arr.join(‘‘);

}

关于稳定性排序的一个结论:

基于比较的简单排序算法,即时间复杂度为O(N^2)的排序算法,通常可认为均是稳定排序
其它先进的排序算法,比如归并排序、堆排序、桶排序之类(通常这类算法的时间复杂度可优化为n*LogN),通常可认为均是不稳定排序

单链表实现

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

<script type="text/javascript">

  function print(msg) {

    document.write(msg);

  }

  function println(msg) {

    print(msg + "<br/>");

  }

  //节点类

  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

</script>

关于邻接矩阵、邻接表的选择:

邻接矩阵、邻接表都是图的基本存储方式,
稀松图情况下(即边远小于顶点情况下),用邻接表存储比较适合(相对矩阵N*N而言,邻接表只存储有值的边、顶点,不存储空值,存储效率更高)
稠密图情况下(即边远大地顶点情况下),用邻接矩阵存储比较适合(数据较多的情况下,要对较做遍历,如果用链表存储,要经常跳来跳去,效率较低)

堆:

几乎完全的二叉树:除了最右边位置上的一个或几个叶子可能缺少的二叉树。在物理存储上,可以用数组来存储,如果A[j]的顶点有左、右子节点,则左节点为A[2j]、右节点为A[2j+1],A[j]的父顶点存储在A[j/2]中

堆:本身是一颗几乎完全的二叉树,而且父节点的值不小于子节点的值。应用场景:优先队列,寻找最大或次最大值;以及把一个新元素插入优先队列。

注:以下所有讨论的堆,约定索引0处的元素仅占位,有效元素从下标1开始

根据堆的定义,可以用以下代码测试一个数组是否为堆:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

//测试数组H是否为堆

//(约定有效元素从下标1开始)

//时间复杂度O(n)

function isHeap(H) {

  if (H.length <= 1) { return false; }

  var half = Math.floor(H.length / 2); //根据堆的性质,循环上限只取一半就够了

  for (var i = 1; i <= half; i++) {

    //如果父节点,比任何一个子节点 小,即违反堆定义

    if (H[i] < H[2 * i] || H[i] < H[2 * i + 1]) {

      return false;

    }

  }

  return true;

}

节点向上调整siftUp

某些情况下,如果堆中的某个元素值改变后(比如 10,8,9,7 变成 10,8,9,20 后,20需要向上调整
),不再满足堆的定义,需要向上调整时,可以用以下代码实现

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

//堆中的节点上移

//(约定有效元素从下标1开始)

function siftUp(H, i) {

  if (i <= 1) {

    return;

  }

  for (var j = i; j > 1; j = Math.floor(j / 2)) {

    var k = Math.floor(j / 2);

    //发现 子节点 比 父节点大,则与父节点交换位置

    if (H[j] > H[k]) {

      var t = H[j];

      H[j] = H[k];

      H[k] = t;

    }

    else {

      //说明已经符合堆定义,调整结束,退出

      return;

    }

  }

}

节点向下调整siftDown (既然有向上调整,自然也有向下调整)

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

//堆中的节点下移

//(约定有效元素从下标1开始)

//时间复杂度O(logN)

function siftDown(H, i) {

  if (2 * i > H.length) { //叶子节点,就不用再向下移了

    return;

  }

  for (var j = 2 * i; j < H.length; j = 2 * j) {

    //将j定位到 二个子节点中较大的那个上(很巧妙的做法)

    if (H[j + 1] > H[j]) {

      j++;

    }

    var k = Math.floor(j / 2);

    if (H[k] < H[j]) {

      var t = H[k];

      H[k] = H[j];

      H[j] = t;

    }

    else {

      return;

    }

  }

}

向堆中添加新元素

?


1

2

3

4

5

6

7

8

//向堆H中添加元素x

//时间复杂度O(logN)

function insert(H, x) {

  //思路:先在数组最后加入目标元素x

  H.push(x);

  //然后向上推

  siftUp(H, H.length - 1);

}

从堆中删除元素

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

//删除堆H中指定位置i的元素

//时间复杂度O(logN)

function remove(H, i) {

  //思路:先把位置i的元素与最后位置的元素n交换

  //然后数据长度减1(这样就把i位置的元素给干掉了,但是整个堆就被破坏了)

  //需要做一个决定:最后一个元素n需要向上调整,还是向下调整

  //依据:比如比原来该位置的元素大,则向上调整,反之向下调整

  var x = H[i]; //先把原来i位置的元素保护起来

  //把最后一个元素放到i位置

  //同时删除最后一个元素(js语言的优越性体现!)

  H[i] = H.pop();

  var n = H.length - 1;

  if (i == n + 1) {

    //如果去掉的正好是最后二个元素之一,

    //无需再调整

    return ;

  }

  if (H[i] > x) {

    siftUp(H, i);

  }

  else {

    siftDown(H, i);

  }

}

//从堆中删除最大项

//返回最大值

//时间复杂度O(logN)

function deleteMax(H) {

  var x = H[1];

  remove(H, 1);

  return x;

}

堆排序

这是一种思路非常巧妙的排序算法,精华在于充分利用了“堆”这种数据结构本身的特点(首元素必然最大),而且每个元素的上移、下调,时间复试度又比较低,仅为O(logN),空间上,也无需借助额外的存储空间,仅在数组自身内部交换元素即可。

思路:

1、先将首元素(即最大元素)与最末尾的元素对调---目的在于,把最大值沉底,下一轮重就不再管它了
2、经过1后,剩下的元素通常已经不再是一个堆了。这时,只要把新的首元素用siftDown下调,调整完以后,新的最大值元素自然又上升到了首元素的位置
3、反复1、2,大的元素逐一沉底,最后整个数组就有序了。
时间复杂度分析:创建堆需要O(n)的代价,每次siftDown代价为O(logN),最多调整n-1个元素,所以总代价为
O(N) + (N-1)O(logN),最终时间复杂度为O(NLogN)

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

//堆中的节点下移

//(约定有效元素从下标1开始)

//i为要调整的元素索引

//n为待处理的有效元素下标范围上限值

//时间复杂度O(logN)

function siftDown(H, i, n) {

  if (n >= H.length) {

    n = H.length;

  }

  if (2 * i > n) { //叶子节点,就不用再向下移了

    return;

  }

  for (var j = 2 * i; j < n; j = 2 * j) {

    //将j定位到 二个子节点中较大的那个上(很巧妙的做法)

    if (H[j + 1] > H[j]) {

      j++;

    }

    var k = Math.floor(j / 2);

    if (H[k] < H[j]) {

      var t = H[k];

      H[k] = H[j];

      H[j] = t;

    }

    else {

      return;

    }

  }

}

//对数组的前n个元素进行创建堆的操作

function makeHeap(A, n) {

  if (n >= A.length) {

    n = A.length;

  }

  for (var i = Math.floor(n / 2); i >= 1; i--) {

    siftDown(A, i, n);

  }

}

//堆排序(非降序排列)

//时间复杂度O(nlogN)

function heapSort(H) {

  //先建堆

  makeHeap(H, H.length);

  for (var j = H.length - 1; j >= 2; j--) {

    //首元素必然是最大的

    //将最大元素与最后一个元素互换,

    //即将最大元素沉底,下一轮不再考虑

    var x = H[1];

    H[1] = H[j];

    H[j] = x;

    //互换后,剩下的元素不再满足堆定义,

    //把新的首元素下调(以便继续维持堆的"形状")

    //调整完后,剩下元素中的最大值必须又浮到了第一位

    //进入下一轮循环

    siftDown(H, 1, j - 1);

  }

  return H;

}

关于建堆,如果明白其中的原理后,也可以逆向思路,反过来做

?


1

2

3

4

5

6

7

8

function makeHeap2(A, n) {

  if (n >= A.length) {

    n = A.length;

  }

  for (var i = Math.floor(n / 2); i <= n; i++) {

    siftUp(A, i);

  }

}

不相交集合查找、合并

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

//定义节点Node类

var Node = function (v, p) {

    this.value = v; //节点的值

    this.parent = p; //节点的父节点

    this.rank = 0; //节点的秩(默认为0)    

}

//查找包含节点x的树根节点 

var find = function (x) {

    var y = x;

    while (y.parent != null) {

      y = y.parent;

    }

    var root = y;

    y = x;

    //沿x到根进行“路径压缩”

    while (y.parent != null) {

      //先把父节点保存起来,否则下一行调整后,就弄丢了

      var w = y.parent;

      //将目标节点挂到根下

      y.parent = root;

      //再将工作指针,还原到 目标节点原来的父节点上,

      //继续向上逐层压缩

      y = w

    }

    return root;

}

//合并节点x,y对应的两个树

//时间复杂度O(m) - m为待合并的子集合数量

var union = function (x, y) {

    //先找到x所属集合的根

    var u = find(x);

    //再找到y所属集合的根

    var v = find(y);

    //把rank小的集合挂到rank大的集合上

    if (u.rank <= v.rank) {

      u.parent = v;

      if (u.rank == v.rank) {

        //二个集合的rank不分伯仲时

        //给"胜"出方一点奖励,rank+1

        v.rank += 1;

      }

    }

    else {

      v.parent = u;

    }

}

归纳法

先来看二个排序的递归实现

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

//选择排序的递归实现

//调用示例: selectionSort([3,2,1],0)

function selectionSortRec(A, i) {

  var n = A.length - 1;

  if (i < n) {

    var k = i;

    for (var j = i + 1; j <= n; j++) {

      if (A[j] < A[k]) {

        k = j

      }

    }

    if (k != i) {

      var t = A[k];

      A[k] = A[i];

      A[i] = t;

    }

    selectionSortRec(A, i + 1);

  }

}

//插入排序递归实现

//调用示例:insertSortRec([4,3,2,1],3);

function insertSortRec(A, i) {

  if (i > 0) {

    var x = A[i];

    insertSortRec(A, i - 1);

    var j = i - 1;

    while (j >= 0 && A[j] > x) {

      A[j + 1] = A[j];

      j--;

    }

    A[j + 1] = x;

  }

}

递归的程序通常易于理解,代码也容易实现,再来看二个小例子:

从数组中,找出最大值

?


1

2

3

4

5

6

7

8

9

10

11

12

//在数组中找最大值(递归实现)

function findMax(A, i) {

  if (i == 0) {

    return A[0];

  }

  var y = findMax(A, i - 1);

  var x = A[i - 1];

  return y > x ? y : x;

}

var A = [1,2,3,4,5,6,7,8,9];

var test = findMax(A,A.length);

alert(test);//返回9

有一个已经升序排序好的数组,检查数组中是否存在二个数,它们的和正好为x ?

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

//5.33 递归实现

//A为[1..n]已经排好序的数组

//x为要测试的和

//如果存在二个数的和为x,则返回true,否则返回false

function sumX(A, i, j, x) {

  if (i >= j) {

    return false;

  }

  if (A[i] + A[j] == x) {

    return true;

  }

  else if (A[i] + A[j] < x) {

    //i后移

    return sumX(A, i + 1, j, x);

  }

  else {

    //j前移

    return sumX(A, i, j - 1, x);

  }

}

var A = [1, 2, 3, 4, 5, 6, 7, 8];

var test1 = sumX(A,0,A.length-1,9);

alert(test1); //返回true

递归程序虽然思路清晰,但通常效率不高,一般来讲,递归实现,都可以改写成非递归实现,上面的代码也可以写成:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

//5.33 非递归实现

function sumX2(A, x) {

  var i = 0, j = A.length - 1;

  while (i < j) {

    if (A[i] + A[j] == x) {

      return true;

    }

    else if (A[i] + A[j] < x) {

      //i后移

      i++;

    }

    else {

      //j前移

      j--;

    }

  }

  return false;

}

var A = [1, 2, 3, 4, 5, 6, 7, 8];

var test2 = sumX2(A,9);

alert(test2);//返回true

递归并不总代表低效率,有些场景中,递归的效率反而更高,比如计算x的m次幂,常规算法,需要m次乘法运算,下面的算法,却将时间复杂度降到了O(logn)

?


1

2

3

4

5

6

7

8

9

10

11

12

13

//计算x的m次幂(递归实现)

//时间复杂度O(logn)

function expRec(x, m) {

  if (m == 0) {

    return 1;

  }

  var y = expRec(x, Math.floor(m / 2));

  y = y * y;

  if (m % 2 != 0) {

    y = x * y

  }

  return y;

}

当然,这其中并不光是递归的功劳,其效率的改进 主要依赖于一个数学常识: x^m =
[x^(m/2)]^2,关于这个问题,还有一个思路很独特的非递归解法,巧妙的利用了二进制的特点

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

//将10进制数转化成2进制

function toBin(dec) {

  var bits = [];

  var dividend = dec;

  var remainder = 0;

  while (dividend >= 2) {

    remainder = dividend % 2;

    bits.push(remainder);

    dividend = (dividend - remainder) / 2;

  }

  bits.push(dividend);

  bits.reverse();

  return bits.join("");

}

//计算x的m次幂(非递归实现)

//很独特的一种解法

function exp(x, m) {

  var y = 1;

  var bin = toBin(m).split(‘‘);

  //先将m转化成2进制形式

  for (var j = 0; j < bin.length; j++) {

    y = y * 2;

    //如果2进制的第j位是1,则再*x

    if (bin[j] == "1") {

      y = x * y

    }

  }

  return y;

}

//println(expRec(2, 5));

//println(exp(2, 5));

再来看看经典的多项式求值问题:

给定一串实数An,An-1,...,A1,A0 和一个实数X,计算多项式Pn(x)的值

著名的Horner公式:

已经如何计算:

显然有:

这样只需要 N次乘法+N次加法

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

//多项式求值

//N次乘法+N次加法搞定,伟大的改进!

function horner(A, x) {

  var n = A.length - 1

  var p = A[n];

  for (var j = 0; j < n; j++) {

    p = x * p + A[n - j - 1];

  }

  return p;

}

//计算: y(2) = 3x^3 + 2x^2 + x -1;

var A = [-1, 1, 2, 3];

var y = horner(A, 2);

alert(y);//33

多数问题

一个元素个数为n的数组,希望快速找出其中大于出现次数>n/2的元素(该元素也称为多数元素)。通常可用于选票系统,快速判定某个候选人的票数是否过半。最优算法如下:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

//找出数组A中“可能存在”的多数元素

function candidate(A, m) {

  var count = 1, c = A[m], n = A.length - 1;

  while (m < n && count > 0) {

    m++;

    if (A[m] == c) {

      count++;

    }

    else {

      count--;

    }

  }

  if (m == n) {

    return c;

  }

  else {

    return candidate(A, m + 1);

  }

}

//寻找多数元素

//时间复杂度O(n)

function majority(A) {

  var c = candidate(A, 0);

  var count = 0;

  //找出的c,可能是多数元素,也可能不是,

  //必须再数一遍,以确保结果正确

  for (var i = 0; i < A.length; i++) {

    if (A[i] == c) {

      count++;

    }

  }

  //如果过半,则确定为多数元素

  if (count > Math.floor(A.length / 2)) {

    return c;

  }

  return null;

}

var m = majority([3, 2, 3, 3, 4, 3]);

alert(m);

以上算法基于这样一个结论:在原序列中去除两个不同的元素后,那么在原序列中的多数元素在新序列中还是多数元素

证明如下:

如果原序列的元素个数为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

下一个问题:全排列

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

function swap(A, i, j) {

  var t = A[i];

  A[i] = A[j];

  A[j] = t;

}

function println(msg) {

  document.write(msg + "<br/>");

}

//全排列算法

function perm(P, m) {

  var n = P.length - 1;

  if (m == n) {

    //完成一个新排列时,输出

    println(P);

    return;

  }

  for (var j = m; j <= n; j++) {

    //将起始元素与后面的每个元素交换

    swap(P, j, m);

    //在前m个元素已经排好的基础上

    //再加一个元素进行新排列

    perm(P, m + 1);

    //把j与m换回来,恢复递归调用前的“现场",

    //否则因为递归调用前,swap已经将原顺序破坏了,

    //导致后面生成排序时,可能生成重复

    swap(P, j, m);

  }

}

perm([1, 2, 3], 0);

//1,2,3

//1,3,2

//2,1,3

//2,3,1

//3,2,1

//3,1,2

分治法

要点:将问题划分成二个子问题时,尽量让子问题的规模大致相等。这样才能最大程度的体现一分为二,将问题规模以对数折半缩小的优势。

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

//打印输出(调试用)

function println(msg) {

  document.write(msg + "<br/>");

}

//数组中i,j位置的元素交换(辅助函数)

function swap(A, i, 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, low, 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, 2, 3, 4, 5, 6, 7, 8], 0, 7);

println(r); //1,8

//二分搜索(分治法实现)

//输入:A为已按非降序排列的数组

//x 为要搜索的值

//low,high搜索的起、止索引范围

//返回:如果找到,返回下标,否则返回-1

function binarySearchDiv(A, x, low, 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, x, low, mid - 1);

  }

  else {

    return binarySearchDiv(A, x, mid + 1, high);

  }

}

var f = binarySearchDiv([1, 2, 3, 4, 5, 6, 7], 4, 0, 6);

println(f); //3

//将数组A,以low位置的元素为界,划分为前后二半

//n为待处理的索引范围上限

function split(A, low, 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, i, j);

      }

    }

  }

  //经过上面的折腾后,除low元素外,其它的元素均以就位

  //最后需要把low与最后一个比low位置小的元素交换,

  //以便把low放在分水岭位置上

  swap(A, low, i);

  return [A, i];

}

var A = [5, 1, 2, 6, 3];

var b = split(A, 0, A.length - 1);

println(b[0]); //3,1,2,5,6

//快速排序 

function quickSort(A, low, high) {

  var w = high;

  if (low < high) {

    var t = split(A, low, w); //分治思路,先分成二半

    w = t[1];

    //在前一半求解

    quickSort(A, low, w - 1);

    //在后一半求解

    quickSort(A, w + 1, high);

  }

}

var A = [5, 6, 4, 7, 3];

quickSort(A, 0, A.length - 1);

println(A); //3,4,5,6,7

split算法的思想应用

设A[1..n]是一个整数集,给出一算法重排数组A中元素,使得所有的负整数放到所有非负整数的左边,你的算法的运行时间应当为Θ(n)

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

function sort1(A) {

  var i = 0, j = A.length - 1;

  while (i < j) {

    if (A[i] >= 0 && A[j] >= 0) {

      j--;

    }

    else if (A[i] < 0 && A[j] < 0) {

      i++;

    }

    else if (A[i] > 0 && A[j] < 0) {

      swap(A, i, j);

      i++;

      j--;

    }

    else {

      i++;

      j--;

    }

  }

}

function sort2(A) {

  if (A.length <= 1) { return; }

  var i = 0;

  for (var j = i + 1; j < A.length; j++) {

    if (A[j] < 0 && A[i] >= 0) {

      swap(A, i, j);

      i++;

    }

  }

}

var a = [1, -2, 3, -4, 5, -6, 0];

sort1(a);

println(a);//-6,-2,-4,3,5,1,0

var b = [1, -2, 3, -4, 5, -6, 0];

sort2(b);

println(b);//-2,-4,-6,1,5,3,0

希望本文所述对大家JavaScript程序设计有所帮助。

时间: 2024-10-19 07:30:19

javascript常用经典算法实例详解的相关文章

javascript中ajax post实例详解

一,原生态的XMLHttpRequest 代码如下 复制代码 <script language="javascript">         function saveintroduce()    {    //获取接受返回信息层    var introducemsg = document.getElementById("introducemsg");    //获取表单对象和用户信息值    var f = document.introduceedt;

Manacher算法 , 实例 详解 . NYOJ 最长回文

Manacher 算法 定义数组 p[i]表示以i为中心

JavaScript 身份证号有效验证详解及实例代码

JavaScript 身份证号有效验证详解及实例代码 这篇文章主要介绍了JavaScript 身份证号有效验证详解及实例代码的相关资料,需要的朋友可以参考下 JavaScript验证身份证号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 <%@ page language="jav

[转]Mahout推荐算法API详解

Mahout推荐算法API详解 Hadoop家族系列文章,主要介绍Hadoop家族产品,常用的项目包括Hadoop, Hive, Pig, HBase, Sqoop, Mahout, Zookeeper, Avro, Ambari, Chukwa,新增加的项目包括,YARN, Hcatalog, Oozie, Cassandra, Hama, Whirr, Flume, Bigtop, Crunch, Hue等. 从2011年开始,中国进入大数据风起云涌的时代,以Hadoop为代表的家族软件,占

《HTML 5网页开发实例详解》目录

第一篇  从宏观上认识HTML 5 讲述了HTML 5引发的Web革命.HTML 5的整体特性.HTML 5相关概念和框架和开发环境搭建. 第1章 HTML 5引发的Web革命 1.1  你是不是真的了解HTML 5 1.1.1  通过W3C认识HTML 5的发展史 1.1.2  HTML 5.HTML4.XHTML的区别 1.1.3  什么人应该学HTML 5 1.1.4  一个图告诉你如何学习HTML 5 1.2  浏览器之争 1.2.1  说说这些常见的浏览器 1.2.2  浏览器的兼容烦

Mahout推荐算法API详解

前言 用Mahout来构建推荐系统,是一件既简单又困难的事情.简单是因为Mahout完整地封装了“协同过滤”算法,并实现了并行化,提供非常简单的API接口:困难是因为我们不了解算法细节,很难去根据业务的场景进行算法配置和调优. 本文将深入算法API去解释Mahout推荐算法底层的一些事. 1. Mahout推荐算法介绍 Mahoutt推荐算法,从数据处理能力上,可以划分为2类: 单机内存算法实现 基于Hadoop的分步式算法实现 1). 单机内存算法实现 单机内存算法实现:就是在单机下运行的算法

免费的HTML5连载来了《HTML5网页开发实例详解》连载(三)DOCTYPE和字符集

在2.1.2节中通过新老DOCTYPE的对比,读者可以清晰地看到HTML 5在精简旧有结构上做出的努力.DOCTYPE在出现之初主要用于XML中,用作描述XML允许使用的元素.属性和排列方式.起初HTML借鉴了XML中DOCTYPE的使用方法,并赋予了新用法,如大家熟知的触发浏览器的标准模式.假使在制作一张页面时,没有设定DOCTYPE,则浏览器会以怪异模式状态进行处理(即Quirks模式),该模式与标准模式在盒模型.样式.布局等都存在较大差异.因此,DOCTYPE在制作页面时是不可或缺的部分.

Spring MVC 3.0.5+Spring 3.0.5+MyBatis3.0.4全注解实例详解(四)

这一章大象将详细分析web层代码,以及使用Spring MVC的注解及其用法和其它相关知识来实现控制器功能.     之前在使用Struts2实现MVC的注解时,是借助struts2-convention这个插件,如今我们使用Spring自带的spring-webmvc组件来实现同样的功能,而且比之以前更简单.另外,还省掉了整合两个框架带来的不稳定因素.     对于Spring MVC框架,我主要讲一下它的常用注解,再结合一些示例进行说明,方便大家能够快速理解.     一.Spring MV

免费的HTML5连载来了《HTML5网页开发实例详解》连载(五)图解通过Fiddler加速开发

Fiddler是Windows底下最强大的请求代理调试工具,监控任何浏览器的HTTP/HTTPS流量,窜改客户端请求和服务器响应,解密HTTPS Web会话,图4.44为Fiddler原理示意图. 图4.44  Fiddler原理示意图 Fiddler安装的系统要求为Windows XP或Windows 8中的版本,其中Fiddler2依赖于Microsoft.NET Framework 2.0,最新的Fiddler4依赖于Microsoft.NET Framework 4.0. Fiddler