大数据算法

1. 

栈(stack)又名堆栈,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

2. 

3. 

栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。

基本概念

定义:栈是限定仅在表头进行插入和删除操作的线性表。要搞清楚这个概念,首先要明白原来的意思,如此才能把握本质。",存储货物或供旅客住宿的地方,可引申为仓库、中转站,所以引入到计算机领域里,就是指数据暂时存储的地方,所以才有进栈、出栈的说法。

首先系统或者数据结构栈中数据内容的读取与插入(压入push和 弹出pop)是两回事!插入是增加数据,弹出是删除数据 ,这些操作只能从栈顶即最低地址作为约束的接口界面入手操作 ,但读取栈中的数据是随便的没有接口约束之说。很多人都误解这个理念从而对栈产生困惑。 [1]  而系统栈在计算机体系结构中又起到一个跨部件交互的媒介区域的作用 即 cpu 与内存的交流通道 ,cpu只从系统给我们自己编写的应用程序所规定的栈入口线性地读取执行指令, 用一个形象的词来形容它就是pipeline(管道线、流水线)。cpu内部交互具体参见 EUBIU的概念介绍。

栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针

栈是允许在同一端进行插入和删除操作的特殊线性表。允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom);栈底固定,而栈顶浮动;栈中元素个数为零时称为空栈。插入一般称为进栈PUSH),删除则称为退栈(POP)。栈也称为后进先出表。

栈可以用来在函数调用的时候存储断点,做递归时要用到栈!

以上定义是在经典计算机科学中的解释。

计算机系统中,栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。在i386机器中,栈顶由称为esp寄存器进行定位。压栈的操作使得栈顶的地址减小,弹出的操作使得栈顶的地址增大。

栈在程序的运行中有着举足轻重的作用。最重要的是栈保存了一个函数调用时所需要的维护信息,这常常称之为堆栈帧或者活动记录堆栈帧一般包含如下几方面的信息:

1函数的返回地址和参数

2. 临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量。

栈的模型

基本算法

1进栈PUSH)算法

TOP≥n时,则给出溢出信息,作出错处理(进栈前首先检查栈是否已满,满则溢出;不满则作);

TOP=TOP+1(栈指针1,指向进栈地址);

③S(TOP)=X,结束(X为新进栈的元素);

2.退栈(POP)算法

TOP≤0,则给出下溢信息,作出错处理(退栈前先检查是否已为空栈, 空则下溢;不空则作②)

②X=S(TOP),(退栈后的元素赋给X):

③TOP=TOP-1,结束(栈指针1,指向栈顶)。

实现

栈分顺序栈和链式栈,下面程序介绍了顺序栈的实现。

pascal

1数组

Const

m=栈表目数的上限;

Type

stack=array[1..m] of stype; {栈类型}

Var

s:stack;{}

top:integer;

2.记录型

const

m=栈表目数的上限;

type

stack=record

elem: array[1..m] of elemtp;

top:0..m; {栈顶指针}

end;

Var

s:stack;{}

C++代码

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

#include<iostream>

classSqStack

{

private:

enum{MaxSize=100};

intdata[MaxSize];

inttop;

public:

SqStack();

~SqStack();

boolisEmpty();

voidpushInt(intx);

intpopInt();

intgetTop();

voiddisplay();

};

SqStack::SqStack()

{

top=-1;

}

SqStack::~SqStack(){}

boolSqStack::isEmpty()//判断栈为空

{

return(top==-1);

}

voidSqStack::pushInt(intx)//元素进栈

{

if(top==MaxSize-1)

{

std::cout<<"栈上溢出!"<<std::endl;

}

else

{

++top;

data[top]=x;

}

}

intSqStack::popInt()//退栈

{

inttmp=0;

if(top==-1)

{

std::cout<<"栈已空!"<<std::endl;

}

else

{

tmp=data[top--];

}

returntmp;

}

intSqStack::getTop()//获得栈顶元素

{

inttmp=0;

if(top==-1)

{

std::cout<<"栈空!"<<std::endl;

}

else

{

tmp=data[top];

}

returntmp;

}

voidSqStack::display()//打印栈里元素

{

std::cout<<"栈中元素:"<<std::endl;

for(intindex=top;index>=0;--index)

{

std::cout<<data[index]<<std::endl;

}

}

intmain()

{

SqStackst;

std::cout<<"栈空:"<<st.isEmpty()<<std::endl;

for(inti=1;i<10;i++)

{

st.pushInt(i);

}

st.display();

std::cout<<"退一次栈"<<std::endl;

st.popInt();

std::cout<<"栈顶元素:"<<st.getTop()<<std::endl;

st.popInt();

st.display();

return0;

}

C代码

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

#include<stdio.h>

#include<malloc.h>

#defineDataTypeint

#defineMAXSIZE1024

typedefstruct

{

DataTypedata[MAXSIZE];

inttop;

}SeqStack;

SeqStack*Init_SeqStack()//栈初始化

{

SeqStack*s;

s=(SeqStack*)malloc(sizeof(SeqStack));

if(!s)

{

printf("空间不足\n");

returnNULL;

}

else

{

s->top=-1;

returns;

}

}

intEmpty_SeqStack(SeqStack*s)//判栈空

{

if(s->top==-1)

return1;

else

return0;

}

intPush_SeqStack(SeqStack*s,DataTypex)//入栈

{

if(s->top==MAXSIZE-1)

return0;//栈满不能入栈

else

{

s->top++;

s->data[s->top]=x;

return1;

}

}

intPop_SeqStack(SeqStack*s,DataType*x)//出栈

{

if(Empty_SeqStack(s))

return0;//栈空不能出栈

else

{

*x=s->data[s->top];

s->top--;

return1;

}//栈顶元素存入*x,返回

}

DataTypeTop_SeqStack(SeqStack*s)//取栈顶元素

{

if(Empty_SeqStack(s))

return0;//栈空

else

returns->data[s->top];

}

intPrint_SeqStack(SeqStack*s)

{

inti;

printf("当前栈中的元素:\n");

for(i=s->top;i>=0;i--)

printf("%3d",s->data[i]);

printf("\n");

return0;

}

intmain()

{

SeqStack*L;

intn,num,m;

inti;

L=Init_SeqStack();

printf("初始化完成\n");

printf("栈空:%d\n",Empty_SeqStack(L));

printf("请输入入栈元素个数:\n");

scanf("%d",&n);

printf("请输入要入栈的%d个元素:\n",n);

for(i=0;i<n;i++)

{

scanf("%d",&num);

Push_SeqStack(L,num);

}

Print_SeqStack(L);

printf("栈顶元素:%d\n",Top_SeqStack(L));

printf("请输入要出栈的元素个数(不能超过%d个):\n",n);

scanf("%d",&n);

printf("依次出栈的%d个元素:\n",n);

for(i=0;i<n;i++)

{

Pop_SeqStack(L,&m);

printf("%3d",m);

}

printf("\n");

Print_SeqStack(L);

printf("栈顶元素:%d\n",Top_SeqStack(L));

return0;

}

定义stack的简单代码:

stack<int> sta;

入栈:sta.push(x);

出栈:sta.pop();

判断栈的大小: sta.size();

判断栈是否为空:sta.empty();

队列

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。

队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out线性表 [1] 

顺序队列

建立顺序队列结构必须为其静态分配或动态申请一片连续的存储空间,并设置两个指针进行管理。一个是队头指针front,它指向队头元素;另一个是队尾指针rear,它指向下一个入队元素的存储位置,如图所示

 

每次在队尾插入一个元素是,rear1;每次在队头删除一个元素时,front1。随着插入和删除操作的进行,队列元素的个数不断变化,队列所占的存储空间也在为队列结构所分配的连续空间中移动。当front=rear时,队列中没有任何元素,称为空队列。当rear增加到指向分配的连续空间之外时,队列无法再插入新元素,但这时往往还有大量可用空间未被占用,这些空间是已经出队的队列元素曾经占用过得存储单元。

顺序队列中的溢出现象:

1) "下溢"现象:当队列为空时,做出队运算产生的溢出现象。下溢是正常现象,常用作程序控制转移的条件。

2"真上溢"现象:当队列满时,做进栈运算产生空间溢出的现象。真上溢是一种出错状态,应设法避免。

3"假上溢"现象:由于入队和出队操作中,头尾指针只增加不减小,致使被删元素的空间永远无法重新利用。当队列中实际的元素个数远远小于向量空间的规模时,也可能由于尾指针已超越向量空间的上界而不能做入队操作。该现象称为"假上溢"现象。

循环队列

在实际使用队列时,为了使队列空间能重复使用,往往对队列的使用方法稍加改进:无论插入或删除,一旦rear指针增1front指针增时超出了所分配的队列空间,就让它指向这片连续空间的起始位置。自己真从MaxSize-11变到0,可用取余运算rear%MaxSizefront%MaxSize来实现。这实际上是把队列空间想象成一个环形空间,环形空间中的存储单元循环使用,用这种方法管理的队列也就称为循环队列。除了一些简单应用之外,真正实用的队列是循环队列。 [2] 

在循环队列中,当队列为空时,有front=rear,而当所有队列空间全占满时,也有front=rear。为了区别这两种情况,规定循环队列最多只能有MaxSize-1个队列元素,当循环队列中只剩下一个空存储单元时,队列就已经满了。因此,队列判空的条件时front=rear,而队列判满的条件时front=rear+1%MaxSize。队空和队满的情况如图:

 

队列的数组实现

队列可以用数组Q[1…m]来存储,数组的上界m即是队列所容许的最大容量。在队列的运算中需设两个指针head,队头指针,指向实际队头元素;tail,队尾指针,指向实际队尾元素的下一个位置。一般情况下,两个指针的初值设为0,这时队列为空,没有元素。数组定义Q[1…10]Q(i) i=3,4,5,6,7,8。头指针head=2,尾指针tail=8。队列中拥有的元素个数为:L=tail-head。现要让排头的元素出队,则需将头指针加1。即head=head+1这时头指针向上移动一个位置,指向Q(3),表示Q(3)已出队。如果想让一个新元素入队,则需尾指针向上移动一个位置。即tail=tail+1这时Q(9)入队。当队尾已经处理在最上面时,即tail=10,如果还要执行入队操作,则要发生"上溢",但实际上队列中还有三个空位置,所以这种溢出称为"假溢出"

克服假溢出的方法有两种。一种是将队列中的所有元素均向低地址区移动,显然这种方法是很浪费时间的;另一种方法是将数组存储区看成是一个首尾相接的环形区域。当存放到n地址后,下一个地址就"翻转"1。在结构上采用这种技巧来存储的队列称为循环队列

队列和栈一样只允许在断点处插入和删除元素。

循环队的入队算法如下:

1tail=tail+1

2、若tail=n+1,则tail=1

3、若head=tail,即尾指针与头指针重合了,表示元素已装满队列,则作上溢出错处理;

4、否则,Q(tail)=X,结束(X为新入出元素)。

队列和栈一样,有着非常广泛的应用。

注意:(1)有时候队列中还会设置表头结点,就是在队头的前面还有一个结点,这个结点的数据域为空,但是指针域指向队头元素。

2)另外,上面的计算还可以利用下面给出的公式cq.rear=(cq.front+1)/max;

当有表头结点时,公式变为cq.rear=(cq.front+1)/max+1)。

队列的链表实现

在队列的形成过程中,可以利用线性链表的原理,来生成一个队列。

基于链表的队列,要动态创建和删除节点,效率较低,但是可以动态增长。

队列采用的FIFO(first in first out),新元素(等待进入队列的元素)总是被插入到链表的尾部,而读取的时候总是从链表的头部开始读取。每次读取一个元素,释放一个元素。所谓的动态创建,动态释放。因而也不存在溢出等问题。由于链表结构体间接而成,遍历也方便。

队列的基本运算

1)初始化队列:Init_Queue(q) ,初始条件:队不存在。操作结果:构造了一个空队;

2)入队操作: In_Queue(q,x),初始条件: 队存在。操作结果: 对已存在的队列q,插入一个元素到队尾,队发生变化;

3)出队操作: Out_Queue(q,x),初始条件存在且非空,操作结果: 删除队首元素,并返回其值,队发生变化;

4)读队头元素:Front_Queue(q,x),初始条件存在且非空,操作结果: 读队头元素,并返回其值,队不变;

5)判队空操作:Empty_Queue(q),初始条件: 队存在,操作结果: 若为空队则返回为1,否则返回为0 [3] 

操作

类型

作用

返回值

例子

length(s)

函数

求字符串s的长度

整型

s:='123456789';

l:=length(s);{l的值为9}

copys,w,k)

函数

复制s中从w开始的k

字符串

s:='123456789';

s1:=copy(s,3,5);{s1的值是'34567'}

val(s,k,code)

过程

将字符串s转为数值,存在k中;code是错误代码


var s:string;k,code:integer;

begin

s:='1234';

val(s,k,code);

write(k);{k=1234}

str(i,s)

过程

将数值i转为字符串s


i:=1234;

str(i,s);

write(s);{s='1234'}

Delete(s,w,k)

过程

s中删除从第w位开始的k个字符


s := 'Honest Abe Lincoln';

Delete(s,8,4);

Writeln(s); { 'Honest Lincoln' }

Insert(s1, S, w)

过程

s1插到s中第w


S := 'Honest Lincoln';

Insert('Abe ', S, 8); { 'Honest Abe Lincoln' }

Pos(c, S)

函数

求字符cs中的位置

整型

S := ' 123.5';

i :=Pos(' ', S);{i的值为1}

+

运算符

将两个字符串连接起来


s1:='1234';

s2:='5678';

s:=s1+s2;{'12345678'}

STL中,对队列的使用很是较完美

下面给出循环队列的运算算法:

(1)将循环队列置为空

//将队列初始化

SeQueue::SeQueue()

{ front=0;

rear=0;

cout<<"init!"<<endl;

}

(2)判断循环队列是否为空

int SeQueue::Empty()

{ if(rear==front) return(1);

else return(0);

}

(3)在循环队列中插入新的元素x

void SeQueue::AddQ(ElemType x)

{ if((rear+1) % MAXSIZE==front) cout<<" QUEUE IS FULL! "<<endl;

else{ rear=(rear+1) % MAXSIZE;

elem[rear]=x;

cout<<" OK!";

}

}

(4)删除队列中队首元素

ElemType SeQueue::DelQ()

{ if(front==rear)

{ cout<<" QUEUE IS EMPTY! "<<endl; return -1;}

else{ front=(front+1) % MAXSIZE;

return(elem[front]);

}

}

(5)取队列中的队首元素

ElemType SeQueue::Front()

{ ElemType x;

if(front== rear)

cout<<"QUEUE IS EMPTY "<<endl;

else x= elem[(front+1)%MAXSIZE];

return (x);

}

链表

线性表的链式存储表示的特点是用一组任意的存储单元存储线性表数据元素(这组存储单元可以是连续的,也可以是不连续的)。因此,为了表示每个数据元素 与其直接后继数据元素 之间的逻辑关系,对数据元素 来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。由这两部分信息组成一个"结点"(如概述旁的图所示),表示线性表中一个数据元素。线性表的链式存储表示,有一个缺点就是要找一个数,必须要从头开始找起,十分麻烦。

根据情况,也可以自己设计链表的其它扩展。但是一般不会在边上附加数据,因为链表的点和边基本上是一一对应的(除了第一个或者最后一个节点,但是也不会产生特殊情况)。不过有一个特例是如果链表支持在链表的一段中把前和后指针反向,反向标记加在边上可能会更方便。

对于非线性的链表,可以参见相关的其他数据结构,例如树、图。另外有一种基于多个线性链表的数据结构:跳表,插入、删除和查找等基本操作的速度可以达到O(nlogn),和平衡二叉树一样。

其中存储数据元素信息的域称作数据域(设域名为data),存储直接后继存储位置的域称为指针域(设域名为next)。指针域中存储的信息又称做指针或链。

由分别表示,,的个结点依次相链构成的链表,称为线性表的链式存储表示,由于此类链表的每个结点中只包含一个指针域,故又称单链表或线性链表。

基本操作

pascal语言

建立

第一行读入n,表示n个数

第二行包括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

37

38

39

40

41

42

43

44

programproject1;

type

point=^node;

node=record

data:longint;

next:point;

end;

var

i,n,e:longint;

p,q,head,last:point;

begin

write('Inputthenumbercount:');

readln(n);

i:=1;

new(head);

read(e);

head^.data:=e;

head^.next:=nil;

last:=head;

q:=head;

whilei<ndo

begin

inc(i);

read(e);

new(p);

q^.next:=p;

p^.data:=e;

p^.next:=nil;

last:=p;

q:=last

end;

//建立链表

q:=head;

whileq^.next<>nildo

begin

write(q^.data,'');

q:=q^.next;

end;

write(q^.data);

//输出

readln;

readln

end.

删除

在以z为头的链表中搜索第一个n,如果找到则删去,返回值为1,否则返回0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

functiondelete(n:longint;varz:point):longint;

var

t,s:point;

begin

t:=z;

while(t^.next<>nil)and(t^.data<>n)do

begin

s:=t;

t:=t^.next;

end;

ift^.data<>nthenexit(0);

s^.next:=t^.next;

dispose(t);

exit⑴

end;

查找

类似于删除,只需要找到不删即可

插入

插入,在以zz为头的链表第w个的前面插入nn元素,函数返回值正常是0,如果w超过了链表的长度,函数返回链表的长度

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

functioninsert(w,nn:longint;varzz:point):longint;

vard:longint;v,vp,vs:point;

begin

v:=zz;

ford:=1towdo

ifv^.next=nil

thenexit(d)

else

begin

vp:=v;

v:=v^.next;

end;

new(vs);

vs^.data:=nn;

vp^.next:=vs;

vs^.next:=v;

exit(0)

end;

链表函数

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

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

#include<stdio.h>

#include<stdlib.h>

#include<iostream.h>

usingnamespacestd;

structNode

{

intdata;//数据域

structNode*next;//指针域

};

/*

Create

*函数功能:创建链表.

*输入:各节点的data

*返回值:指针head

*/

Node*Create()

{

intn=0;

Node*head,*p1,*p2;

p1=p2=newNode;

cin>>p1->data;

head=NULL;

while(p1->data!=0)

{

if(n==0)

{

head=p1;

}

else

p2->next=p1;

p2=p1;

p1=newNode;

cin>>p1->data;

n++;

}

p2->next=NULL;

returnhead;

}

/*

insert

*函数功能:在链表中插入元素.

*输入:head链表头指针,p新元素插入位置,x新元素中的数据域内容

*返回值:无

*/

voidinsert(Node*head,intp,intx)

{

Node*tmp=head;//for循环是为了防止插入位置超出了链表长度

for(inti=0;i<p;i++)

{

if(tmp==NULL)

return;

if(i<p-1)

tmp=tmp->next;

}

Node*tmp2=newNode;

tmp2->data=x;

tmp2->next=tmp->next;

tmp->next=tmp2;

}

/*

del

*函数功能:删除链表中的元素

*输入:head链表头指针,p被删除元素位置

*返回值:被删除元素中的数据域.如果删除失败返回-1

*/

intdel(Node*head,intp)

{

Node*tmp=head;

for(inti=0;i<p;i++)

{

if(tmp==NULL)

return-1;

if(i<p-1)

tmp=tmp->next;

}

intret=tmp->next->data;

tmp->next=tmp->next->next;

returnret;

}

voidprint(Node*head)

{

for(Node*tmp=head;tmp!=NULL;tmp=tmp->next)

printf("%d",tmp->data);

printf("\n");

}

intmain()

{

Node*head;

head=newNode;

head->data=-1;

head->next=NULL;

return0;

}

例子

#include<iostream>

#defineNULL0

structstudent

{

longnum;

structstudent*next;

};

intmain()

{

inti,n;

student*p=(structstudent*)malloc(sizeof(structstudent));

student*q=p;

printf("输入几个值");

scanf("%d",&n);

for(i=1;i<=n;i++)

{

scanf("%d",&(q->num));

q->next=(structstudent*)malloc(sizeof(structstudent));

q=q->next;

}

printf("值第几个");

intrank;

scanf("%d%d",&(q->num),&rank);

student*w=p;

for(i=1;i<rank-1;i++)

{

w=w->next;

}

q->next=w->next;

w->next=q;

for(i=1;i<=n+1;i++)

{

printf("%d",p->num);

p=p->next;

}

return0;

}//指针后移麻烦链表形式循环链表

循环链表是与单链表一样,是一种链式的存储结构,所不同的是,循环链表的最后一个结点的指针是指向该循环链表的第一个结点或者表头结点,从而构成一个环形的链。

循环链表的运算与单链表的运算基本一致。所不同的有以下几点:

1、在建立一个循环链表时,必须使其最后一个结点的指针指向表头结点,而不是象单链表那样置为NULL。此种情况还使用于在最后一个结点后插入一个新的结点。

2、在判断是否到表尾时,是判断该结点链域的值是否是表头结点,当链域值等于表头指针时,说明已到表尾。而非象单链表那样判断链域值是否为NULL

双向链表

双向链表其实是单链表的改进。

当我们对单链表进行操作时,有时你要对某个结点的直接前驱进行操作时,又必须从表头开始查找。这是由单链表结点的结构所限制的。因为单链表每个结点只有一个存储直接后继结点地址的链域,那么能不能定义一个既有存储直接后继结点地址的链域,又有存储直接前驱结点地址的链域的这样一个双链域结点结构呢?这就是双向链表。

在双向链表中,结点除含有数据域外,还有两个链域,一个存储直接后继结点地址,一般称之为右链域;一个存储直接前驱结点地址,一般称之为左链域。

应用举例概述

约瑟夫环问题:已知n个人(以编号123...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。例如:n = 9,k = 1,m = 5

参考代码

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

#include<stdio.h>

#include<malloc.h>

#defineN41

#defineM5

typedefstructnode*link;

structnode

{

intitem;

linknext;

};

linkNODE(intitem,linknext)

{

linkt=malloc(sizeof*t);

t->item=item;

t->next=next;

returnt;

}

intmain(void)

{

inti;

linkt=NODE(1,NULL);

t->next=t;

for(i=2;i<=N;i++)

t=t->next=NODE(i,t->next);

while(t!=t->next)

{

for(i=1;i<M;i++)

t=t->next;

t->next=t->next->next;

}

printf("%d\n",t->item);

return0;

}

其他相关结语与个人总结

C语言是学习数据结构的很好的学习工具。理解了C中用结构体描述数据结构,那么对于理解其C++描述,Java描述都就轻而易举了!

 

链表的提出主要在于顺序存储中的插入和删除的时间复杂度是线性时间的,而链表的操作则可以是常数时间的复杂度。对于链表的插入与删除操作,个人做了一点总结,适用于各种链表如下:

插入操作处理顺序:中间节点的逻辑,后节点逻辑,前节点逻辑。按照这个顺序处理可以完成任何链表的插入操作。

删除操作的处理顺序:前节点逻辑,后节点逻辑,中间节点逻辑。

按照此顺序可以处理任何链表的删除操作。

如果不存在其中的某个节点略过即可。

上面的总结,大家可以看到一个现象,就是插入的顺序和删除的顺序恰好是相反的,很有意思!

操作

-----悉尼大学工程学院张志刚Stone Cold)作品

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

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

14

#include<stdio.h>

#include<stdlib.h>

#include<conio.h>

typedefstructSlist

{

intdata;

structSlist*next;

}

SLIST;

SLIST*InitList_Sq()/*初始化函数*/

{

inta;

SLIST*h,*s,*r;

h=(SLIST*)malloc(sizeof(SLIST));/*建立头指针,头指针不可以更改!!!*/

r=h;

if(!h)

{

printf("分配失败");

exit(0);

}

scanf("%d",&a);

for(;a!=-1;)

{

s=(SLIST*)malloc(sizeof(SLIST));/*每次都开辟一个结点空间并赋值*/

s->data=a;

r->next=s;

r=s;

scanf("%d",&a);

}

r->next='\0';

returnh;

}

voidprint_list(SLIST*finder)/*打印函数*/

{

while(finder!='\0')

{

printf("->%d",finder->data);

finder=finder->next;

}

printf("->end\n");

}

intDeleteNode(SLIST*killer)//删除节点函数

{

inti,j=0;

SLIST*p,*q;

intx;

p=killer;

q=killer->next;

printf("请输入您要删除的节点序号:");

scanf("%d",&i);

while((p->next!='\0')&&(j<i-1))

{

p=p->next;

j++;

q=p->next;

}

if(p->next=='\0'||j>i-1)

{

printf("\nerror");

return-1;

}

else

{

p->next=q->next;

x=q->data;

free(q);

returnx;

}

}

voidInsert_Node(SLIST*jumper)//插入函数,本算法为前插结点法

{

intt,e,j=0;

SLIST*p,*q;

p=jumper;

printf("请输入要插入位置的序号:");

scanf("%d",&t);

printf("请输入要插入的元素:");

scanf("%d",&e);

while(p->next!='\0'&&j<t-1)

{

j++;

p=p->next;

}

if(p=='\0'||j>t-1)

printf("插入的目的位置不存在");

else

{

q=(SLIST*)malloc(sizeof(SLIST));

q->data=e;

q->next=p->next;

p->next=q;

}

}

voidLocate_List(SLIST*reader)//查找值为e的元素

{

inte,i=0;

SLIST*p;

p=reader;

printf("请输入要查找的元素:");

scanf("%d",&e);

while(p->next!='\0'&&p->data!=e)

{

i++;

p=p->next;

}

if(p->data==e)

printf("此元素在%d号位置\n",i);

else

printf("无此元素!");

}

voidmain()

{

inti,k,y;

SLIST*head;

printf("\n1.建立线性表");

printf("\n2.在i位置插入元素e");

printf("\n3.删除第i个元素,返回其值");

printf("\n4.查找值为e的元素");

printf("\n5.结束程序运行");

printf("\n===================================================");

printf("请输入您的选择:");

scanf("%d",&k);

switch(k)

{

case1:

{

head=InitList_Sq();

print_list(head->next);

}break;

case2:

{

head=InitList_Sq();

print_list(head->next);

Insert_Node(head);

print_list(head->next);

}

break;

case3:

{

head=InitList_Sq();

print_list(head->next);

y=DeleteNode(head);

print_list(head->next);

if(y!=-1)

printf("被删除元素为:%d",y);

}break;//头结点不算,从有数据的开始算第一个

case4:

{

head=InitList_Sq();

print_list(head->next);

Locate_List(head);

}break;

}

}

本程序可在微软VC++下编译通过并且运行

使用方法简介:运行程序后,先打数字1,然后回车,这样就可以先创建一个新的链表,比如你要创建一个

4->5->6->7这样一个链表,你就输入数字4回车,输入5回车,输入6回车,输入7回车,最后输入-1回车,这个-1就是告诉程序到此为止的标志

假如你要使用插入的功能位置插入,就输入3,回车,程序会问你插入的数值是什么,比如你要插入999,然后回车,999就被插进去了

其他的功能都大同小异

Data_structures

 集合

 容器

 

 数组

 关联数组

 Multimap

 

 多重集

 散列表

 树状数组

 

 列表

 链表

 队列

 堆栈

 循环队列

 跳跃列表

 

 

 二叉查找树

 

 线段树

 红黑树

 AVL树

 

 

 有向无环图

 二元决策图

 无向图

词条标签:

科学百科信息科学分类 , 中国电子学会 , 

二叉树

 

在计算机科学中,二叉树是每个结点最多有两个子树的树结构。通常子树被称作左子树left subtree)和右子树right subtree)。二叉树常被用于实现二叉查找树和二叉堆。

二叉树的每个结点至多只有二棵子树(不存在度大于2的结点),二叉树的子树有左右之分,次序不能颠倒。二叉树的第i层至多有2^{i-1}个结点;深度为k的二叉树至多有2^k-1个结点;对任何一棵二叉树T,如果其终端结点数为n_0,度为2的结点数为n_2,则n_0=n_2+1

一棵深度为k,且有2^k-1个节点的二叉树,称为满二叉树。这种树的特点是每一层上的节点数都是最大节点数。而在一棵二叉树中,除最后一层外,若其余层都是满的,并且最后一层或者是满的,或者是在右边缺少连续若干节点,则此二叉树为完全二叉树。具有n个节点的完全二叉树的深度为log2n+1。深度为k的完全二叉树,至少有2^(k-1)个节点,至多有2^k-1个节点。

中文名

二叉树

外文名

Binary Tree

    

计算机中数据结构的一种

    

每个结点最多有两个子树的树结构

目录

00001. 1 定义

00002. 2 基本概念

00003.  类型

00004.  相关术语

00005.  二叉树性质

00001.  存储结构

00002.  辨析

00003. 3 遍历顺序

00004.  先序遍历

00005.  中序遍历

00001.  后序遍历

00002.  层次遍历

00003.  线索二叉树

00004. 4 实现演示

定义

编辑

二叉树在图论中是这样定义的:二叉树是一个连通的无环图,并且每一个顶点的度不大于3。有根二叉树还要满足根结点的度不大于2。有了根结点之后,每个顶点定义了唯一的父结点,和最多2个子结点。然而,没有足够的信息来区分左结点和右结点。如果不考虑连通性,允许图中有多个连通分量,这样的结构叫做森林。

基本概念

编辑

二叉树是递归定义的,其结点有左右子树之分,逻辑上二叉树有五种基本形态:

(1)空二叉树——如图(a)

 

(2)只有一个根结点的二叉树——如图(b)

(3)只有左子树——如图(c)

(4)只有右子树——如图(d)

(5)完全二叉树——如图(e)

注意:尽管二叉树与树有许多相似之处,但二叉树不是树的特殊情形。 [1] 

类型

(1)完全二叉树——若设二叉树的高度为h,除第 层外,其它各层 (1h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树

(2)满二叉树——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。

(3)平衡二叉树——平衡二叉树又被称为AVL树(区别于AVL算法),它是一棵二叉排序树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树 [2] 

相关术语

树的结点:包含一个数据元素及若干指向子树的分支;

孩子结点:结点的子树的根称为该结点的孩子;

双亲结点:结点是结点的孩子,则A结点是结点的双亲;

兄弟结点:同一双亲的孩子结点; 堂兄结点:同一层上结点;

祖先结点从根到该结点的所经分支上的所有结点子孙结点:以某结点为根的子树中任一结点都称为该结点的子孙

结点层:根结点的层定义为1;根的孩子为第二层结点,依此类推;

树的深度:树中最大的结点层

结点的度:结点子树的个数

树的度: 树中最大的结点度。

叶子结点:也叫终端结点,是度为 0 的结点;

分枝结点:度不为0的结点;

有序树:子树有序的树,如:家族树;

无序树:不考虑子树的顺序; [3] 

二叉树性质

(1) 非空二叉树中,第i层的结点总数不超过

  

, i>=1

(2) 深度为h的二叉树最多有

  

个结点(h>=1),最少有h个结点;

(3) 对于任意一棵二叉树,如果其叶结点数为N0,而度数为2的结点总数为N2,则N0=N2+1

(4) 具有n个结点的完全二叉树的深度为

  

(注:[ ]表示向下取整)

(5)N个结点的完全二叉树各结点如果用顺序方式存储,则结点之间有如下关系:

I为结点编号则 如果I>1,则其父结点的编号为I/2

如果2*I<=N,则其左儿子(即左子树的根结点)的编号为2*I;若2*I>N,则无左儿子;

如果2*I+1<=N,则其右儿子的结点编号为2*I+1;若2*I+1>N,则无右儿子。

(6)给定N个节点,能构成h(N)种不同的二叉树。

h(N)卡特兰数的第N项。h(n)=C(2*nn)/(n+1)

(7)设有i个枝点,I为所有枝点的道路长度总和,J为叶的道路长度总和J=I+2i [4] 

存储结构

(1)顺序存储方式

1

2

3

4

5

typenode=record

data:datatype

l,r:integer;

end;

vartr:array[1..n]ofnode;

(2)链表存储方式,如:

1

2

3

4

5

typebtree=^node;

node=record

data:datatye;

lchild,rchild:btree;

end;

 

1

辨析

二叉树不是树的一种特殊情形,尽管其与树有许多相似之处,但树和二

 

二叉树(3张)

 叉树有两个主要差别:

1. 树中结点的最大度数没有限制,而二叉树结点的最大度数为2

2. 树的结点无左、右之分,而二叉树的结点有左、右之分。

遍历顺序

编辑

遍历是对树的一种最基本的运算,所谓遍历二叉树,就是按一定的规则和顺序走遍二叉树的所有结点,使每一个结点都被访问一次,而且只被访问一次。由于二叉树是非线性结构,因此,树的遍历实质上是将二叉树的各个结点转换成为一个线性序列来表示。

LDR分别表示遍历左子树、访问根结点和遍历右子树, 则对一棵二叉树的遍历有三种情况:DLR(称为先根次序遍历),LDR(称为中根次序遍历),LRD (称为后根次序遍历)。

先序遍历

首先访问根,再先序遍历左(右)子树,最后先序遍历右(左)子树,C语言代码如下:

1

2

3

4

5

6

7

voidXXBL(tree*root){

//DoSomethingwithroot

if(root->lchild!=NULL)

XXBL(root->lchild);

if(root->rchild!=NULL)

XXBL(root->rchild);

}

中序遍历

首先中序遍历左(右)子树,再访问根,最后中序遍历右(左)子树,C语言代码如下

1

2

3

4

5

6

7

voidZXBL(tree*root)

{

if(root->lchild!=NULL)

ZXBL(root->lchild);//DoSomethingwithroot

if(root->rchild!=NULL)

ZXBL(root->rchild);

}

后序遍历

首先后序遍历左(右)子树,再后序遍历右(左)子树,最后访问根,C语言代码如下

1

2

3

4

5

6

voidHXBL(tree*root){

if(root->lchild!=NULL)

HXBL(root->lchild);

if(root->rchild!=NULL)

HXBL(root->rchild);//DoSomethingwithroot

}

层次遍历

即按照层次访问,通常用队列来做。访问根,访问子女,再访问子女的子女(越往后的层次越低)(两个子女的级别相同)

线索二叉树

线索二叉树(保留遍历时结点在任一序列的前驱和后继的信息):若结点有左子树,则其lchild域指示其左孩子,否则令lchild域指示其前驱;若结点有右子树,则其rchild域指示其右孩子,否则令rchild指示其后继。还需在结点结构中增加两个标志域LTagRTagLTag=0时,lchild域指示结点的左孩子,LTag=1时,lchild域指示结点的前驱;RTag=0时,rchild域指示结点的右孩子,RTag=1时,rchild域指示结点的后继。以这种结点结构构成的二叉线索链表,链表作为二叉树的存储结构,叫做其中指向结点前驱和后继的指针叫做线索,加上线索的二叉树称为线索二叉树。对二叉树以某种次序遍历使其变为线索二叉树的过程叫做线索化。若对二叉树进行中序遍历,则所得的线索二叉树称为中序线索二叉树,线索链表称为为中序线索链表。线索二叉树是一种物理结构

线索二叉树的存储结构

在中序线索树找结点后继的规律是:若其右标志为1,则右链为线索,指示其后继,否则遍历其右子树时访问的第一个结点(右子树最左下的结点)为其后继;找结点前驱的规律是:若其左标志为1,则左链为线索,指示其前驱,否则遍历左子树时最后访问的一个结点(左子树中最右下的结点)为其前驱。

在后序线索树中找到结点的后继分三种情况:

若结点是二叉树的根,则其后继为空;若结点是其双亲的右孩子,或是其双亲的左孩子且其双亲没有右子树,则其后继即为双亲结点;若结点是其双亲的左孩子,且其双亲有右子树,则其后继为双亲右子树上按后序遍历列出的第一个结点。

数据结构定义为:

/*二叉线索存储表示*/typedefenum{Link,Thread}PointerTag;/* Link(0):指针,Thread(1):线索*/typedefstruct BiThrNode{ TElemType data;struct BiThrNode *lchild,*rchild;/*左右孩子指针*/PointerTag LTag,RTag;/* 左右标志 */}BiThrNode,*BiThrTree;

实现演示

编辑

范例二叉树:

A

B C

D E

此树的顺序结构为:ABCD##E

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

 

int main()

{

    node*p=newnode;

    node*p=head;

    head=p;

    stringstr;

    cin >>str;

    creat(p,str,0)//默认根节点在str下标0的位置

    return 0;

}

//p为树的根节点(已开辟动态内存),str为二叉树的顺序存储数组ABCD##E或其他顺序存储数组,r当前结点所在顺序存储数组位置

int main()

{

    node* p = newnode;

    node* p = head;

    head = p;

    string str;

    cin >> str;

    creat(p, str, 0)//默认根节点在str下标0的位置

    return 0;

}

//p为树的根节点(已开辟动态内存),str为二叉树的顺序存储数组ABCD##E或其他顺序存储数组,r当前结点所在顺序存储数组位置

void creat(node* p, string str, int r)

{

    p->data = str[r];

    if (str[r * 2 + 1] == '#' || r * 2 + 1 > str.size() - 1)p->lch = NULL;

    else

    {

        p->lch = newnode;

        creat(p->lch, str, r * 2 + 1);

    }

    if (str[r * 2 + 2] == '#' || r * 2 + 2 > str.size() - 1)p->rch = NULL;

    else

    {

        p->rch = newnode;

        creat(p->rch, str, r * 2 + 2);

    }

}

词条图册更多图册

 

词条图片(3)

 

二叉树(3)

 

计算机科学中的树

二叉树

 二叉树

 二叉查找树

 笛卡尔树

 Top tree

 T树

 

自平衡二叉查找树

 AA树

 AVL树

 红黑树

 伸展树

 树堆

 节点大小平衡树

 

B树

 B树

 B+树

 B*树

 Bx树

 UB树

 2-3树

 2-3-4树

 (a,b)-树

 Dancing tree

 H树

 

Trie

 前缀树

 后缀树

 基数树

 

空间划分树

 四叉树

 八叉树

 k-d树

 vp-树

 R树

 R*树

 R+树

 X树

 M树

 线段树

 希尔伯特R树

 优先R树

 

非二叉树

 Exponential tree

 Fusion tree

 区间树

 PQ tree

 Range tree

 SPQR tree

 Van Emde Boas tree

 

其他类型

 

 散列树

 Finger tree

 Metric tree

 Cover tree

 BK-tree

 Doubly-chained tree

 iDistance

 Link-cut tree

 树状数组

参考资料

红黑树

红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组

它是在1972年由Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的红黑树

红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。

它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的是树中元素的数目。

中文名

红黑树

外文名

RED BLACK TREE

    

自平衡二叉查找树

    

实现关联数组

发明人

鲁道夫·贝尔

发明时间

1972

    

对称二叉B

目录

00001. 1 数据结构

00002. 2 树的旋转

00001. 3 性质

00002. 4 术语

00001. 5 用途

00002. 6 操作

数据结构

它的统计性能要好于平衡二叉树(有些书籍根

红黑树

据作者姓名,Adelson-VelskiiLandis,将其称为AVL-),因此,红黑树在很多地方都有应用。在C++ STL中,很多部分(包括set, multiset, map, multimap)应用了红黑树的变体(SGI STL中的红黑树有一些变化,这些修改提供了更好的性能,以及对set操作的支持)。其他平衡树还有:AVLSBT伸展树TREAP 等等。

树的旋转

当我们在对红黑树进行插入和删除等操作时,对树做了修改,那么可能会违背红黑树的性

 

树的左旋(2张)

 质。

为了保持红黑树的性质,我们可以通过对树进行旋转,即修改树中某些结点的颜色及指针结构,以达到对红黑树进行插入、删除结点等操作时,红黑树依然能保持它特有的性质(五点性质)。

如右图。

性质

红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:

性质1. 节点是红色或黑色。

性质2. 根节点是黑色。

性质每个叶节点(NIL节点,空节点)是黑色的。

性质每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)

性质5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

这些约束强制了红黑树的关键性质从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。

要知道为什么这些特性确保了这个结果,注意到性质4导致了路径不能有两个毗连的红色节点就足够了。最短的可能路径都是黑色节点,最长的可能路径有交替的红色和黑色节点。因为根据性质5所有最长的路径都有相同数目的黑色节点,这就表明了没有路径能多于任何其他路径的两倍长。

在很多树数据结构的表示中,一个节点有可能只有一个子节点,而叶子节点不包含数据。用这种范例表示红黑树是可能的,但是这会改变一些属性并使算法复杂。为此,本文中我们使用 "nil 叶子"(null)叶子",如上图所示,它不包含数据而只充当树在此结束的指示。这些节点在绘图中经常被省略,导致了这些树好象同上述原则相矛盾,而实际上不是这样。与此有关的结论是所有节点都有两个子节点,尽管其中的一个或两个可能是空叶子。

术语

红黑树是一种特定类型的二叉树,它是在计算机科学中用来组织数据比如数字的块的一种结构。所有数据块都存储在节点中。这些节点中的某一个节点总是担当起始位置的功能,它不是任何节点的儿子,我们称之为根节点或根。它有最多两个"儿子",都是它连接到的其他节点。所有这些儿子都可以有自己的儿子,以此类推。这样根节点就有了把它连接到在树中任何其他节点的路径。

如果一个节点没有儿子,我们称之为叶子节点,因为在直觉上它是在树的边缘上。子树是从特定节点可以延伸到的树的某一部分,其自身被当作一个树。在红黑树中,叶子被假定为 null 或空。

由于红黑树也是二叉查找树,它们当中每一个节点的比较值都必须大于或等于在它的左子树中的所有节点,并且小于或等于在它的右子树中的所有节点。这确保红黑树运作时能够快速的在树中查找给定的值。

用途

红黑树和AVL树一样都对插入时间、删除时间和查找时间提供了最好可能的最坏情况担保。这不只是使它们在时间敏感的应用如即时应用(real time application)中有价值,而且使它们有在提供最坏情况担保的其他数据结构中作为建造板块的价值;例如,在计算几何中使用的很多数据结构都可以基于红黑树。

红黑树在函数编程中也特别有用,在这里它们是最常用的持久数据结构之一,它们用来构造关联数组和集合,在突变之后它们能保持为以前的版本。除了O(log n)的时间之外,红黑树的持久版本对每次插入或删除需要O(log n)的空间。

红黑树是 2-3-4的一种等同。换句话说,对于每个 2-3-4 树,都存在至少一个数据元素是同样次序的红黑树。在 2-3-4 树上的插入和删除操作也等同于在红黑树中颜色翻转和旋转。这使得 2-3-4 树成为理解红黑树背后的逻辑的重要工具,这也是很多介绍算法的教科书在红黑树之前介绍 2-3-4 树的原因,尽管 2-3-4 树在实践中不经常使用。

操作

在红黑树上只读操作不需要对用于二叉查找树的操作做出修改,因为它也是二叉查找树。但是,在插入和删除之后,红黑属性可能变得违规。恢复红黑属性需要少量(O(log n))的颜色变更(这在实践中是非常快速的)并且不超过三次树旋转(对于插入是两次)。这允许插入和删除保持为 O(log n) 次,但是它导致了非常复杂的操作。 [1] 

词条图册更多图册

 

词条图片(3)

 

树的左旋(2)

 

Data_structures

 集合

 容器

 

 数组

 关联数组

 Multimap

 

 多重集

 散列表

 树状数组

 

 列表

 链表

 队列

 堆栈

 循环队列

 跳跃列表

 

 

 二叉查找树

 

 线段树

 红黑树

 AVL树

 

 

 有向无环图

 二元决策图

 无向图

计算机科学中的树

二叉树

 二叉树

 二叉查找树

 笛卡尔树

 Top tree

 T树

 

自平衡二叉查找树

 AA树

 AVL树

 红黑树

 伸展树

 树堆

 节点大小平衡树

 

B树

 B树

 B+树

 B*树

 Bx树

 UB树

 2-3树

 2-3-4树

 (a,b)-树

 Dancing tree

 H树

 

Trie

 前缀树

 后缀树

 基数树

 

空间划分树

 四叉树

 八叉树

 k-d树

 vp-树

 R树

 R*树

 R+树

 X树

 M树

 线段树

 希尔伯特R树

 优先R树

 

非二叉树

 Exponential tree

 Fusion tree

 区间树

 PQ tree

 Range tree

 SPQR tree

 Van Emde Boas tree

 

其他类型

 

 散列树

 Finger tree

 Metric tree

 Cover tree

 BK-tree

 Doubly-chained tree

 iDistance

 Link-cut tree

 树状数组

参考资料

B树

B-树中查找给定关键字的方法是,首先把根结点取来,在根结点所包含的关键字K1,…,Kn查找给定的关键字(可用顺序查找或二分查找法),若找到等于给定值的关键字,则查找成功;否则,一定可以确定要查找的关键字在KiKi+1之间,Pi为指向子树根节点的指针,此时取指针Pi所指的结点继续查找,直至找到,或指针Pi为空时查找失败。

中文名

B

外文名

B tree

    

B-树、B_

提出者

R.BayerE.mccreight

提出时间

1970

应用学科

计算机

适用领域范围

软件

适用领域范围

编程

目录

00001. 1 定义

00002. 2 性能分析

00003. 3 示例

定义

B树 [1]

1970年,R.BayerE.mccreight提出了一种适用于外查找的,它是一种平衡的多叉树,称为B树(或B-树、B_树)。

一棵mB(balanced tree of order m)是一棵平衡的m路搜索树。它或者是空树,或者是满足下列性质的树:

1、根结点至少有两个子女;

2、每个非根节点所包含的关键字个数 满足:┌m/2┐ - 1 <= j <= m - 1

3、除根结点以外的所有结点(不包括叶子结点)的度数正好是关键字总数加1,故内部子树个数 满足:┌m/2┐ <= k <= m 

4、所有的叶子结点都位于同一层。

B-树中,每个结点中关键字从小到大排列,并且当该结点的孩子是非叶子结点时,该k-1个关键字正好是k个孩子包含的关键字的值域的分划。

因为叶子结点不包含关键字,所以可以把叶子结点看成在树里实际上并不存在外部结点,指向这些外部结点的指针为空,叶子结点的数目正好等于树中所包含的关键字总个数加1

B-树中的一个包含n个关键字,n+1个指针的结点的一般形式为: (n,P0,K1,P1,K2,P2,…,Kn,Pn

其中,Ki为关键字,K1<K2<…<Kn, Pi 是指向包括KiKi+1之间的关键字的子树的指针。

性能分析

B-树包含N个关键字,因此有N+1个叶子结点,叶子都在第I层。因为根至少有两个孩子,因此第二层至少有两个结点。除根和叶子外,其它结点至少有┌m/2┐个孩子,因此在第三层至少有2*┌m/2┐个结点,在第四层至少有2*(┌m/2┐^2)个结点,...,在第I层至少有2*(┌m/2┐^(l-2) )个结点,于是有:

N+1 ≥ 2*┌m/2┐I-2

考虑第L层的结点个数为N+1,那么2*(┌m/2┐^(l-2)≤N+1,也就是L层的最少结点数刚好达到N+1

即: I≤ log┌m/2┐((N+1)/2 )+2

所以,当B-树包含N个关键关键字时,B-树的最大高度为l-1(因为计算B-树高度时,叶结点所在层不计算在内)

即:log┌m/2┐((N+1)/2 )+1

这个公式保证了B-树的查找效率是相当高的。

示例

当在叶子结点处于第L+1层的B树中插入关键字时,被插入的关键字总是进入第L层的结点。

若在一个包含j<m-1个关键字的结点中插入一个新的关键字,则把新的关键字直接插入该结点即可;但若把一个新的关键字插入到包含m-1mB-树的阶)个关键字的结点中,则将引起结点的分裂。在这种情况下,要把这个结点分裂为两个,并把中间的一个关键字(中间的关键字满足:左边的小于该关键字;右边的大于该关键字;故正好可以作为双亲)拿出来插到该结点的双亲结点中去,双亲结点也可能是满的,就需要再分裂、再往上插,从而可能导致B-树可能朝着根的方向生长。

插入算法演示:插入之前如图1

插入之前的B树

插入之后如图2

插入之后的B树

4. B-树的删除:

当从B-树中删除一个关键字Ki时,总的分为以下两种情况:

如果该关键字所在的结点不是最下层的非叶子结点,则先需要把此关键字与它在B-树中后继对换位置,即以指针Pi所指子树中的最小关键字Y代替Ki,然后在相应的结点中删除Y

如果该关键字所在的结点正好是最下层的非叶子结点,这种情况下,会有以下两种可能:

① 若该关键字Ki所在结点中的关键字个数不小于┌m/2┐,则可以直接从该结点中删除该关键字和相应指针即可。

② 若该关键字Ki所在结点中的关键字个数小于┌m/2┐,则直接从结点中删除关键字会导致此结点中所含关键字个数小于┌m/2┐-1 。这种情况下,需考察该结点在B树中的左或右兄弟结点,从兄弟结点中移若干个关键字到该结点中来(这也涉及它们的双亲结点中的一个关键字要作相应变化),使两个结点中所含关键字个数基本相同;但如果其兄弟结点的关键字个数也很少,刚好等于┌m/2┐-1 ,这种移动则不能进行,这种情形下,需要把删除了关键字Ki的结点、它的兄弟结点及它们双亲结点中的一个关键字合并为一个结点。

词条图册更多图册

 

词条图片(5)

 

计算机科学中的树

二叉树

 二叉树

 二叉查找树

 笛卡尔树

 Top tree

 T树

 

自平衡二叉查找树

 AA树

 AVL树

 红黑树

 伸展树

 树堆

 节点大小平衡树

 

B树

 B树

 B+树

 B*树

 Bx树

 UB树

 2-3树

 2-3-4树

 (a,b)-树

 Dancing tree

 H树

 

Trie

 前缀树

 后缀树

 基数树

 

空间划分树

 四叉树

 八叉树

 k-d树

 vp-树

 R树

 R*树

 R+树

 X树

 M树

 线段树

 希尔伯特R树

 优先R树

 

非二叉树

 Exponential tree

 Fusion tree

 区间树

 PQ tree

 Range tree

 SPQR tree

 Van Emde Boas tree

 

其他类型

 

 散列树

 Finger tree

 Metric tree

 Cover tree

 BK-tree

 Doubly-chained tree

 iDistance

 Link-cut tree

 树状数组

参考资料

· 1.部分内容来自《数据结构》第二版,清华大学出版社

插入排序

有一个已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,这个时候就要用到一种新的排序方法——插入排序法,插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,时间复杂度O(n^2)。是稳定的排序方法。插入算法把要排序的数组分成两部分:第一部分包含了这个数组的所有元素,但将最后一个元素除外(让数组多一个空间才有插入的位置),而第二部分就只包含这一个元素(即待插入元素)。在第一部分排序完成后,再将这个最后元素插入到已排好序的第一部分中。

插入排序的基本思想是:每步将一个待排序的记录,按其关键码值的大小插入前面已经排序的文件中适当位置上,直到全部插入完为止。

中文名

插入排序

外文名

insertion sorting

    

排序方法

    

直接插入排序,二分插入排序

目录

00001. 1 相关术语

00002.  关键码

00003.  内部排序和外部排序

00004. 2 分类

00005.  直接插入排序

00006.  折半插入排序(二分插入排序)

00001. 3 原理

00002. 4 设计步骤

00003. 5 描述

00004. 6 实现

00005.  伪代码

00001.  C语言

00002.  PHP版本

00003.  Java版本

00004.  C#版本

00005.  Pascal版本

00006.  Ruby版本

00001.  Scala版本

00002. 7 算法复杂度

00003. 8 稳定性

相关术语

关键码

关键码是数据元素中某个数据项的值,用它可以标示一个数据元素。通常会用记录来标示数据元素,一个记录可以有若干数据项组成。例如,一个学生的信息就是一条记录,它包括学号,姓名,性别等若干数据项。主关键码可以唯一的标示一个记录的关键码,如学号。次关键码是可以标示若干记录的关键字,如性别、姓名。

假设一个文件有n条记录{},对应的关键码是{},排序家就是将此n个记录按照关键码的大小递增(或递减)的次序排列起来,使这些记录由无序变为有序的一种操作。排序后的序列若为{}时,其对应的关键码值满足{}{}

若在待排序的记录中,存在两个或两个以上的关键码值相等的记录,经排序后这些记录的相对次序仍然保持不变,则称相应的排序方法是稳定的方法,否则是不稳定的方法。

内部排序和外部排序

根据排序过程中涉及的存储器不同,可以讲排序方法分为两大类:一类是内部排序,指的是待排序的几率存放在计算机随机存储器中进行的排序过程;另一类的外部排序,指的是排序中要对外存储器进行访问的排序过程。

内部排序是排序的基础,在内部排序中,根据排序过程中所依据的原则可以将它们分为5类:插入排序、交换排序、选择排序、归并排序和基数排序;根据排序过程的时间复杂度来分,可以分为三类:简单排序、先进排序、基数排序。

评价排序算法优劣的标准主要是两条:一是算法的运算量,这主要是通过记录的比较次数和移动次数来反应;另一个是执行算法所需要的附加存储单元的的多少。

分类

包括:直接插入排序,二分插入排序(又称折半插入排序),链表插入排序,希尔排序(又称缩小增量排序)。属于稳定排序的一种(通俗地讲,就是两个相等的数不会交换位置) 

直接插入排序

直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。 [1] 

例如,已知待排序的一组记录是:

60,71,49,11,24,3,66

假设在排序过程中,前3个记录已按关键码值递增的次序重新排列,构成一个有序序列:

49,60,71

将待排序记录中的第4个记录(即11)插入上述有序序列,以得到一个新的含4个记录的有序序列。首先,应找到11的插入位置,再进行插入。可以讲11放入数组的第一个单元r[0]中,这个单元称为监视哨,然后从71起从右到左查找,11小于71,将71右移一个位置,11小于60,又将60右移一个位置,11小于49,又再将49右移一个位置,这时再将11r[0]的值比较,11≥r[0],它的插入位置就是r[1]。假设11大于第一个值r[1]。它的插入位置应该在r[1]r[2]之间,由于60已经右移了,留出来的位置正好留给11.后面的记录依照同样的方法逐个插入到该有序序列中。若记录数n,续进行n-1趟排序,才能完成。

直接插入排序的算法思路:

1) 设置监视哨r[0],将待插入记录的值赋值给r[0]

2) 设置开始查找的位置j

3) 在数组中进行搜索,搜索中将第j个记录后移,直至r[0].key≥r[j].key为止;

4) 将r[0]插入r[j+1]的位置上。

直接插入排序算法:

public void zjinsert (Redtype r[],int n)

{

int I,j;

Redtype temp;

for (i=1;i<n;i++)

{

temp = r[i];

j=i-1;

while (j>-1 &&temp.key<r[j].key)

{

r[j+1]=r[j];

j--;

}

r[j+1]=temp;

}

}

折半插入排序(二分插入排序)

将直接插入排序中寻找A[i]的插入位置的方法改为采用折半比较,即可得到折半插入排序算法。在处理A[i]时,A[0]……A[i-1]已经按关键码值排好序。所谓折半比较,就是在插入A[i]时,取A[i-1/2]的关键码值与A[i]的关键码值进行比较,如果A[i]的关键码值小于A[i-1/2]的关键码值,则说明A[i]只能插入A[0]A[i-1/2]之间,故可以在A[0]A[i-1/2-1]之间继续使用折半比较;否则只能插入A[i-1/2]A[i-1]之间,故可以在A[i-1/2+1]A[i-1]之间继续使用折半比较。如此担负,直到最后能够确定插入的位置为止。一般在A[k]A[r]之间采用折半,其中间结点为A[k+r/2],经过一次比较即可排除一半记录,把可能插入的区间减小了一半,故称为折半。执行折半插入排序的前提是文件记录必须按顺序存储。 [2] 

折半插入排序的算法思想:

算法的基本过程:

1)计算 0 ~ i-1 的中间点,用 索引处的元素与中间值进行比较,如果 索引处的元素大,说明要插入的这个元素应该在中间值和刚加入i索引之间,反之,就是在刚开始的位置 到中间值的位置,这样很简单的完成了折半;

2)在相应的半个范围里面找插入的位置时,不断的用(1)步骤缩小范围,不停的折半,范围依次缩小为 1/2 1/4 1/8 .......快速的确定出第 个元素要插在什么地方;

3)确定位置之后,将整个序列后移,并将元素插入到相应位置。

希尔排序法

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。

各组内的排序通常采用直接插入法。由于开始时s的取值较大,每组内记录数较少,所以排序比较快。随着不断增大,每组内的记录数逐步增多,但由于已经按排好序,因此排序速度也比较快。

原理

n个元素的数列分为已有序和无序两个部分,如

插入排序过程示例

下所示:

{{a1}{a2a3a4an}}

{{a1⑴a2⑴}{a3⑴a4⑴ …an⑴}}

{{a1(n-1),a2(n-1) …},{an(n-1)}}

每次处理就是将无序数列的第一个元素与有序数列的元素从后往前逐个进行比较,找出插入位置,将该元素插入到有序数列的合适位置中。

假设在一个无序的数组中,要将该数组中的数按插入排序的方法从小到大排序。假设啊a[]={3,5,2,1,4};插入排序的思想就是比大小,满足条件交换位置,一开始会像冒泡排序一样,但会比冒泡多一步就是交换后(a[i]=a[i+1]后)原位置(a[i])会继续和前面的数比较满足条件交换,直到a[i+1]前面的数组是有序的。比如在第二次比较后数组变成a[]={2,3,5,1,4};

设计步骤

算法设计有很多方法。插入排序使用的是增量(incremental)方法;在排好子数组A[1..j-1]后,将A[j]插入,形成排好序的子数组A[1..j]

步骤

从有序数列和无序数列{a2,a3an}开始进行排序;

处理第i个元素时(i=2,3n),数列{a1,a2ai-1}是已有序的,而数列{ai,ai+1an}是无序的。用aiai-1a i-2a1进行比较,找出合适的位置将ai插入;

重复第二步,共进行n-i次插入处理,数列全部有序。

思路

假定这个数组的序是排好的,然后从头往后,如果有数比当前外层元素的值大,则将这个数的位置往后挪,直到当前外层元素的值大于或等于它前面的位置为止.这具算法在排完前k个数之后,可以保证a[1…k]是局部有序的,保证了插入过程的正确性.

描述

一般来说,插入排序都采用in-place数组上实现。具体算法描述如下:

⒈ 从第一个元素开始,该元素可以认为已经被排序

⒉ 取出下一个元素,在已经排序的元素序列中从后向前扫描

⒊ 如果该元素(已排序)大于新元素,将该元素移到下一位置

⒋ 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置

⒌ 将新元素插入到下一位置中

⒍ 重复步骤2~5

如果比较操作的代价比交换操作大的话,可以采用二分查找法来减少比较操作的数目。该算法可以认为是插入排序的一个变种,称为二分查找排序。

1

实现

伪代码

INSERTION-SORT(A)

1forj=2tolength[A]

2dokey=A[j]

3 //InsertA[j] into the sorted sequenceA[1..j-1].

i=j-1

5 whilei>0 andA[i] >key

6 doA[i+1] =A[i]

i=i-1

A[i+1] =key

C语言

示例代码为C语言,输入参数中,需要排序的数组array[ ],起始索引为first(数组有序部分最后一个的下标,或者直接标0),终止索引为last(数组元素的最后一个的下标)。示例代码的函数采用insert-place排序,调用完成后,array[]中从firstlast处于升序排列。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

voidinsertion_sort(intarray[],intfirst,intlast)

{

inti,j;

inttemp;

for(i=first+1;i<last;i++)

{

temp=array[i];

j=i-1;

//与已排序的数逐一比较,大于temp时,该数移后

while((j>=0)&&(array[j]>temp))

{

array[j+1]=array[j];

j--;

}

//存在大于temp的数

if(j!=i-1)

{array[j+1]=temp;}

}

}

这个更好:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

voidinsert_sort(int*array,unsignedintn)

{

inti,j;

inttemp;

for(i=1;i<n;i++)

{

temp=*(array+i);

for(j=i;j>0&&*(array+j-1)>temp;j--)

{

*(array+j)=*(array+j-1);

}

*(array+j)=temp;

}

}

c++版本

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

#include<iterator>

template<typenamebiIter>

voidinsertion_sort(biIterbegin,biIterend)

{

typedeftypenamestd::iterator_traits<biIter>::value_typevalue_type;

biIterbond=begin;

std::advance(bond,1);

for(;bond!=end;std::advance(bond,1)){

value_typekey=*bond;

biIterins=bond;

biIterpre=ins;

std::advance(pre,-1);

while(ins!=begin&&*pre>key){

*ins=*pre;

std::advance(ins,-1);

std::advance(pre,-1);

}

*ins=key;

}

}

PHP版本

1

2

3

4

5

6

7

8

9

10

11

12

13

functioninsertSort($arr){

for($i=1;$i<count($arr);$i++){

$tmp=$arr[$i];

$key=$i-1;

while($key>=0&&$tmp<$arr[$key]){

$arr[$key+1]=$arr[$key];

$key--;

}

if(($key+1)!=$i)

$arr[$key+1]=$tmp;

}

return$arr;

}

Java版本

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

/**

*插入排序

*@paramarr

*@return

*/

private static int[] insertSort(int[]arr){

if(arr == null || arr.length < 2){

    return arr;

}

for(inti=1;i<arr.length;i++){

for(intj=i;j>0;j--){

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

//TODO:

int temp=arr[j];

arr[j]=arr[j-1];

arr[j-1]=temp;

}else{

//接下来是无用功

break;

}

}

}

return arr;

}

运行软件:eclipse

C#版本

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

classProgram

{

staticvoidMain(string[]args)

{

InsertionSort();

}

///<summary>

///插入排序法

///</summary>

privatestaticvoidInsertionSort()

{

Console.WriteLine("插入排序法");

inttemp=0;

int[]arr={23,44,66,76,98,11,3,9,7};

Console.WriteLine("排序前的数组:");

foreach(intiteminarr)

{

Console.Write(item+",");

}

Console.WriteLine();

varlength=arr.Length;

for(inti=1;i<length;i++)

{

for(intj=i;j>0;j--)

{

if(arr[j]>arr[j-1])

{

temp=arr[j];

arr[j]=arr[j-1];

arr[j-1]=temp;

}

}

//每次排序后数组

PrintResult(arr);

}

Console.ReadKey();

}

///<summary>

///打印结果

///</summary>

///<paramname="arr"></param>

privatestaticvoidPrintResult(IEnumerable<int>arr)

{

foreach(intiteminarr)

{

Console.Write(item+",");

}

Console.WriteLine();

}

}

Pascal版本

procedure insertsort(var R:filetype);

//R[1..N]按递增序进行插入排序,R[0]是监视哨

begin

for I := 2 To n do //依次插入R[2],...,R[n]

begin

R[0]:=R[I];j:=l-1;

while R[0] < R[J] Do //查找R[I]的插入位置

begin

R[j+1]:=R[j]; //将大于R[I]的元素后移

j:=j-1;

end;

R[j+1]:=R[0] ; //插入R[I]

end;

end; //InsertSort

(运行软件:Free Pascal IDE 2.6.4)

Ruby版本

1

2

3

4

5

6

7

8

9

10

11

12

def insertion_sort(array)  

   array.each_with_index do |element, index|    

     next if index == 0 #第一个元素默认已排序    

     j = index - 1    

     while j >= 0 && array[j] > element      

       array[j + 1] = array[j] 

       j -= 1    

     end    

     array[j + 1] = element   

   end  

   array

end

Scala版本

1

2

3

4

5

6

7

8

9

10

11

definsertSort(ilist:Array[Int]){

for(i<-1untililist.length){

for(j<-(0untili).reverse){

if(ilist(j+1)<ilist(j)){

valtmp=ilist(j+1)

ilist(j+1)=ilist(j)

ilist(j)=tmp

}

}

}

}

算法复杂度

如果目标是把n个元素的序列升序排列,那么采用插入排序存在最好情况和最坏情况。最好情况就是,序列已经是升序排列了,在这种情况下,需要进行的比较操作需(n-1)次即可。最坏情况就是,序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。插入排序的赋值操作是比较操作的次数加上 (n-1)次。平均来说插入排序算法的时间复杂度为O(n^2)。因而,插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,例如,量级小于千,那么插入排序还是一个不错的选择。 [3] 

稳定性

插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。

原地排序

 希尔排序

 冒泡排序

 插入排序

 选择排序

 堆排序

桶排序

桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(Θn))。但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响。

中文名

桶排序

    

数据的长度必须完全一样

    

Data=rand()/10000+10000

数据结构设计

链表可以采用很多种方式实现

    

平均情况下桶排序以线性时间运行

    

桶排序利用函数的映射关系

    

计算机算法

目录

00001. 1 定义

00002. 2 算法

00003. 3 代价

00001. 4 源码

00002.  C++

00003.  JAVA

00004.  PAS

00001. 5 应用

00002.  海量数据

00003.  典型

定义

假定:输入是由一个随机过程产生的[0, 1)区间上均匀分布的实数。将区间[0, 1)划分为n个大小相等的子区间(桶),每桶大小1/n[0, 1/n), [1/n, 2/n), [2/n, 3/n)[k/n, (k+1)/n )n个输入元素分配到这些桶中,对桶中元素进行排序,然后依次连接桶输入0 ≤A[1..n] <1辅助数组B[0..n-1]是一指针数组,指向桶(链表)。

算法

桶排序算法要求,数据的长度必须完全一样,程序过程要产生长度相同的数据,使用下面的方法:Data=rand()/10000+10000上面提到的,每次下一次的扫描顺序是按照上次扫描的结果来的,所以设计上提供相同的两个桶数据结构。前一个保存每一次扫描的结果供下次调用,另外一个临时拷贝前一次扫描的结果提供给前一个调用。

数据结构设计:链表可以采用很多种方式实现,通常的方法是动态申请内存建立结点,但是针对这个算法,桶里面的链表结果每次扫描后都不同,就有很多链表的分离和重建。如果使用动态分配内存,则由于指针的使用,安全性低。所以,笔者设计时使用了数组来模拟链表(当然牺牲了部分的空间,但是操作却是简单了很多,稳定性也大大提高了)。共十个桶,所以建立一个二维数组,行向量的下标0—9代表了10个桶,每个行形成的一维数组则是桶的空间。

平均情况下桶排序以线性时间运行。像基数排序一样,桶排序也对输入作了某种假设, 因而运行得很快。具 体来说,基数排序假设输入是由一个小范围内的整数构成,而桶排序则 假设输入由一个随机过程产生,该过程将元素一致地分布在区间[01)上。 桶排序的思想就是把区间[01)划分成n个相同大小的子区间,或称桶,然后将n个输入数分布到各个桶中去。因为输入数均匀分布在[01)上,所以一般不会有很多数落在一个桶中的情况。为得到结果,先对各个桶中的数进行排序,然后按次序把各桶中的元素列出来即可。

在桶排序算法的代码中,假设输入是含n个元素的数组A,且每个元素满足0≤ A[i]<1。另外还需要一个辅助数组B[O..n-1]来存放链表实现的桶,并假设可以用某种机制来维护这些表。

桶排序的算法如下(伪代码表示),其中floor(x)是地板函数,表示不超过x的最大整数。

procedure Bin_Sort(var A:List);begin

n:=length(A);

for i:=1 to n do

A[i]插到表B[floor(n*A[i])];

for i:=0 to n-1 do

插入排序对表B[i]进行排序;

将表B[0],B[1],...,B[n-1]按顺序合并;

end;

右图演示了桶排序作用于有10个数的输入数组上的操作过程。(a)输入数组A[1..10](b)在该算法的第5行后的有序表()数组B[0..9]。桶i中存放了区间[i/10(i+1)/10]上的值。排序输出由表B[O]B[1]...B[9]的按序并置构成。

要说明这个算法能正确地工作,看两个元素A[i]A[j]。如果它们落在同一个桶中,则它们在输出序列中有着正确的相对次序,因为它们所在的桶是采用插入排序的。现假设它们落到不同的桶中,设分别为B[i']B[j']。不失一般性,假设i' i'=floor(n*A[i])≥floor(n*A[j])=j' 得矛盾 (因为i' 来分析算法的运行时间。除第5行外,所有各行在最坏情况的时间都是O(n)。第5行中检查所有桶的时间是O(n)。分析中唯一有趣的部分就在于第5行中插人排序所花的时间。

为分析插人排序的时间代价,设ni为表示桶B[i]中元素个数的随机变量。因为插入排序以二次时间运行,故为排序桶B[i]中元素的期望时间为E[O(ni2)]=O(E[ni2]),对各个桶中的所有元素排序的总期望时间为:O(n)

 

(1) 为了求这个和式,要确定每个随机变量ni的分布。我们共有n个元素,n个桶。某个元素落到桶B[i]的概率为l/n,因为每个桶对应于区间[01)l/n。这种情况与投球的例子很类似:有n个球 (元素)n个盒子 (),每次投球都是独立的,且以概率p=1/n落到任一桶中。这样,ni=k的概率就服从二项分布B(k;n,p),其期望值为E[ni]=np=1,方差V[ni]=np(1-p)=1-1/n。对任意随机变量X,有右图所示表达式

(2)将这个界用到(1)式上,得出桶排序中的插人排序的期望运行时间为O(n)。因而,整个桶排序的期望运行时间就是线性的。

代价

桶排序利用函数的映射关系,减少了几乎所有的比较工作。实际上,桶排序的f(k)值的计算,其作用就相当于快排中划分,已经把大量数据分割成了基本有序的数据块()。然后只需要对桶中的少量数据做先进的比较排序即可。

N关键字进行桶排序的时间复杂度分为两个部分:

(1) 循环计算每个关键字的桶映射函数,这个时间复杂度O(N)

(2) 利用先进的比较排序算法对每个桶内的所有数据进行排序,其时间复杂度 ∑ O(Ni*logNi) 。其中Ni 为第i个桶的数据量。

很显然,第(2)部分是桶排序性能好坏的决定因素。尽量减少桶内数据的数量是提高效率的唯一办法(因为基于比较排序的最好平均时间复杂度只能达到O(N*logN))。因此,我们需要尽量做到下面两点:

(1) 映射函数f(k)能够将N个数据平均的分配到M个桶中,这样每个桶就有[N/M]个数据量。

(2) 尽量的增大桶的数量。极限情况下每个桶只能得到一个数据,这样就完全避开了桶内数据的比较排序操作。 当然,做到这一点很不容易,数据量巨大的情况下,f(k)函数会使得桶集合的数量巨大,空间浪费严重。这就是一个时间代价和空间代价的权衡问题了。

对于N个待排数据,M个桶,平均每个桶[N/M]个数据的桶排序平均时间复杂度为:

O(N)+O(M*(N/M)*log(N/M))=O(N+N*(logN-logM))=O(N+N*logN-N*logM)

N=M时,即极限情况下每个桶只有一个数据时。桶排序的最好效率能够达到O(N)

总结:桶排序的平均时间复杂度为线性的O(N+C),其中C=N*(logN-logM)。如果相对于同样的N,桶数量M越大,其效率越高,最好的时间复杂度达到O(N)。当然桶排序的空间复杂度O(N+M),如果输入数据非常庞大,而桶的数量也非常多,则空间代价无疑是昂贵的。此外,桶排序是稳定的。 [1] 

源码

io.open();//打开控制台

/**-------------------------------------------------------* 桶排序**------------------------------------------------------*/

/*

桶排序假设输入元素均匀而独立分布在区间[0,1) 即 0 <= x and x < 1;将区间划分成n个相同大小的子区间(),然后将n个输入按大小分布到各个桶中去,对每个桶内部进行排序。最后将所有桶的排序结果合并起来.

*/

//插入排序算法

insert_sort = function( array ){

for( right=2;#array ) {

var top = array[right];

//Insert array[right] into the sorted seqquence array[1....right-1]

var left = right -1;

while( left and array[left]>top){

array[left+1] = array[left];

left--;

}

array[left+1] = top;

}

return array;

}

//排序算法

bucket_sort = function( array ){

var n = #array;

var bucket ={}

for(i=0;n;1){

bucket[i] = {} //创建一个桶

}

var bucket_index

for(i=1;n;1){

bucket_index = math.floor(n * array[i]);

table.push( bucket [ bucket_index ],array[i] );//放到桶里去

}

for(i=1;n;1){

insert_sort( bucket[i] ); //对每个桶进行插入排序

}

return table.concat( table.unpack(bucket) )

}

io.print("----------------")

io.print("桶排序")

io.print("----------------")

array={};

//桶排序假设输入是由一个随机过程产生的小数.

math.randomize()

for(i=1;20;1){

table.push( array,math.random() )

}

//排序

array = bucket_sort( array )

//输出结果

for(i=1;#array;1){

io.print( array[i] )

}

execute("pause") //任意键继续

io.close();//关闭控制台

C++

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

4

#include<iostream>

usingnamespace std;

int a[]={1,255,8,6,25,47,14,35,58,75,96,158,657};

const int len=sizeof(a)/sizeof(int);

int b[10][len+1]={0};//将b全部置0

void bucketSort(int a[]);//桶排序函数

void distribute Elments(int a[],int b[10][len+1],int digits);

void collectElments(int a[],int b[10][len+1]);

int numOfDigits(int a[]);

void zeroBucket(int b[10][len+1]);//将b数组中的全部元素置0

int main()

{

cout<<"原始数组:";

for(int i=0;i<len;i++)

cout<<a[i]<<",";

cout<<endl;

bucketSort(a);

cout<<"排序后数组:";

for(int i=0;i<len;i++)

cout<<a[i]<<",";

cout<<endl;

return 0;

}

void bucketSort(int a[])

{

int digits=numOfDigits(a);

for(int i=1;i<=digits;i++)

{

distributeElments(a,b,i);

collectElments(a,b);

if(i!=digits)

zeroBucket(b);

}

}

int numOfDigits(int a[])

{

int largest=0;

for(int i=0;i<len;i++)//获取最大值

if(a[i]>largest)

largest=a[i];

int digits=0;//digits为最大值的位数

while(largest)

{

digits++;

largest/=10;

}

return digits;

}

void distributeElments(int a[],int b[10][len+1],int digits)

{

int divisor=10;//除数

for(int i=1;i<digits;i++)

divisor*=10;

for(int j=0;j<len;j++)

{

int numOfDigist=(a[j]%divisor-a[j]%(divisor/10))/(divisor/10);

//numOfDigits为相应的(divisor/10)位的值,如当divisor=10时,求的是个位数

int num=++b[numOfDigist][0];//用b中第一列的元素来储存每行中元素的个数

b[numOfDigist][num]=a[j];

}

}

void collectElments(int a[],int b[10][len+1])

{

int k=0;

for(int i=0;i<10;i++)

for(int j=1;j<=b[i][0];j++)

a[k++]=b[i][j];

}

void zeroBucket(int b[][len+1])

{

for(int i=0;i<10;i++)

for(int j=0;j<len+1;j++)

b[i][j]=0;

}

JAVA

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

 

public static void basket(int data[])//data为待排序数组

{

int n=data.length;

int bask[][]=new int[10][n];

int index[]=new int[10];

int max=Integer.MIN_VALUE;

for(int i=0;i<n;i++)

{

max=max>(Integer.toString(data[i]).length())?max:(Integer.toString(data[i]).length());

}

String str;

for(int i=max-1;i>=0;i--)

{

for(int j=0;j<n;j++)

{

str="";

if(Integer.toString(data[j]).length()<max)

{

for(int k=0;k<max-Integer.toString(data[j]).length();k++)

str+="0";

}

str+=Integer.toString(data[j]);

bask[str.charAt(i)-'0'][index[str.charAt(i)-'0']++]=data[j];

}

int pos=0;

for(int j=0;j<10;j++)

{

for(int k=0;k<index[j];k++)

{

data[pos++]=bask[j][k];

}

}

for(intx=0;x<10;x++)index[x]=0;

}

}

PAS

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

program barrel_sort;

var

a:array[0..100of integer;

i,k,n:integer;

begin

for i:=0 to 100 do a[i]:=0;{初始化}

readln(n);

for i:=1 to do

begin

read(k);

a[k]:=a[k]+1;{将等于k的值全部装入第k桶中}

end;

for i:=0 to 100 do

while a[i]>0 do

begin

write(i:6);

a[i]:=a[i]-1;

end;{输出排序结构}

end.

//这段应该是鸽巢排序,桶排序应该有段桶内比较排序的函数

应用

海量数据

一年的全国高考考生人数为500 万,分数使用标准分,最低100 ,最高900 ,没有小数,要求对这500 万元素的数组进行排序。

分析:对500W数据排序,如果基于比较的先进排序,平均比较次数为O(5000000*log5000000)≈1.112亿。但是我们发现,这些数据都有特殊的条件: 100=<score<=900。那么我们就可以考虑桶排序这样一个投机取巧的办法、让其在毫秒级别就完成500万排序。

方法:创建801(900-100)个桶。将每个考生的分数丢进f(score)=score-100的桶中。这个过程从头到尾遍历一遍数据只需要500W次。然后根据桶号大小依次将桶中数值输出,即可以得到一个有序的序列。而且可以很容易的得到100分有***人,501分有***人。

实际上,桶排序对数据的条件有特殊要求,如果上面的分数不是从100-900,而是从0-2亿,那么分配2亿个桶显然是不可能的。所以桶排序有其局限性,适合元素值集合并不大的情况。

典型

在一个文件中有10G个整数,乱序排列,要求找出中位数。内存限制为2G。只写出思路即可(内存限制为2G意思是可以使用2G空间来运行程序,而不考虑本机上其他软件内存占用情况。) 关于中位数:数据排序后,位置在最中间的数值。即将数据分成两部分,一部分大于该数值,一部分小于该数值。中位数的位置:当样本数为奇数时,中位数=(N+1)/2 ; 当样本数为偶数时,中位数为N/21+N/2的均值(那么10G个数的中位数,就第5G大的数与第5G+1大的数的均值了)。

分析:既然要找中位数,很简单就是排序的想法。那么基于字节的桶排序是一个可行的方法。

思想:将整型的每1byte作为一个关键字,也就是说一个整形可以拆成4keys,而且最高位的keys越大,整数越大。如果高位keys相同,则比较次高位的keys。整个比较过程类似于字符串的字典序

第一步:10G整数每2G读入一次内存,然后一次遍历这536,870,912即(1024*1024*1024*2 /4个数据。每个数据用位运算">>"取出最高8(31-24)。这8bits(0-255)最多表示256个桶,那么可以根据8bit的值来确定丢入第几个桶。最后把每个桶写入一个磁盘文件中,同时在内存中统计每个桶内数据的数量NUM[256]

代价:(1) 10G数据依次读入内存的IO代价(这个是无法避免的,CPU不能直接在磁盘上运算)(2)在内存中遍历536,870,912个数据,这是一个O(n)的线性时间复杂度(3)256个桶写回到256个磁盘文件空间中,这个代价是额外的,也就是多付出一倍的10G数据转移的时间。

第二步:根据内存中256个桶内的数量NUM[256],计算中位数在第几个桶中。很显然,2,684,354,560个数中位数是第1,342,177,280个。假设前127个桶的数据量相加,发现少于1,342,177,280,把第128个桶数据量加上,大于1,342,177,280。说明,中位数必在磁盘的第128个桶中。而且在这个桶的第1,342,177,280-N(0-127)个数位上。N(0-127)表示前127个桶的数据量之和。然后把第128个文件中的整数读入内存。(若数据大致是均匀分布的,每个文件的大小估计在10G/256=40M左右,当然也不一定,但是超过2G的可能性很小)。注意,变态的情况下,这个需要读入的第128号文件仍然大于2G,那么整个读入仍然可以按照第一步分批来进行读取。

代价:(1)循环计算255个桶中的数据量累加,需要O(M)的代价,其中m<255(2)读入一个大概80M左右文件大小的IO代价。

第三步:继续以内存中的某个桶内整数的次高8bit(他们的最高8bit是一样的)进行桶排序(23-16)。过程和第一步相同,也是256个桶。

第四步:一直下去,直到最低字节(7-0bit)的桶排序结束。我相信这个时候完全可以在内存中使用一次快排就可以了。

整个过程的时间复杂度O(n)的线性级别上(没有任何循环嵌套)。但主要时间消耗在第一步的第二次内存-磁盘数据交换上,即10G数据分255个文件写回磁盘上。一般而言,如果第二步过后,内存可以容纳下存在中位数的某一个文件的话,直接快排就可以了(修改者注:我想,继续桶排序但不写回磁盘,效率会更高?)。 [2] 

参考资料

· 1.桶排序介绍

· 2.桶排序及其应用

堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。在数组的非降序排序中,需要使用的就是大根堆,因为根据大根堆的要求可知,最大的值一定在堆顶。

中文名

堆排序

外文名

Heapsort

    

排序算法

发明人

罗伯特·弗洛伊德

起源于

罗伯特·弗洛伊德

目录

00001. 1 起源

00002. 2 

00003.  定义

00004.  高度

00005. 3 简介

00006.  注意

00001.  特点

00002. 4 算法分析

00003.  平均性能

00004.  其他性能

00005. 5 示例代码

00006.  C语言

00001.  C++

00002.  Java

00003.  Python

00004.  C#

00005.  JavaScript

00006.  Pascal代码

起源

编辑

[1]  1991年的计算机先驱奖获得者、斯坦福大学计算机科学系教授罗伯特·弗洛伊德(Robert WFloyd)和威廉姆斯(JWilliams)在1964年共同发明了著名的堆排序算法( Heap Sort )

编辑

定义

n个关键字序列KlK2Kn称为(Heap),当且仅当该序列满足如下性质(简称为堆性质):

(1)ki<=k(2i)且ki<=k(2i+1)(1≤i≤ n/2),当然,这是小根堆,大根堆则换成>=(2)ki>=k(2i)且ki>=k(2i+1)(1≤i≤ n/2)。//k(i)相当于二叉树的非叶子结点K(2i)则是左子节点,k(2i+1)是右子节点

若将此序列所存储的向量R[1..n]看做是一棵完全二叉树存储结构,则堆实质上是满足如下性质的完全二叉树:

 

树中任一非叶子结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。

【例】关键字序列(101556253070)和(705630251510)分别满足堆性质(1)和(2),故它们均是堆,其对应的完全二叉树分别如小根堆示例和大根堆示例所示。

大根堆和小根堆:根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最小者的堆称为小根堆,又称最小堆。根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大根堆,又称最大堆。注意:堆中任一子树亦是堆。以上讨论的堆实际上是二叉堆Binary Heap),类似地可定义k叉堆。

高度

堆可以被看成是一棵树,结点在堆中的高度可以被定义为从本结点到叶子结点的最长简单下降路径上边的数目;定义堆的高度为树根的高度。我们将看到,堆结构上的一些基本操作的运行时间至多是与树的高度成正比,为Olgn)。

简介

编辑

堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。

1)用大根堆排序的基本思想

① 先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区

② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key

由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。

……

直到无序区只有一个元素为止。

2)大根堆排序算法的基本操作:

建堆,建堆是不断调整堆的过程,从len/2处开始调整,一直到第一个节点,此处len是堆中元素的个数。建堆的过程是线性的过程,从len/20处一直调用调整堆的过程,相当于o(h1)+o(h2)…+o(hlen/2) 其中h表示节点的深度,len/2表示节点的个数,这是一个求和的过程,结果是线性的O(n)

调整堆:调整堆在构建堆的过程中会用到,而且在堆排序过程中也会用到。利用的思想是比较节点i和它的孩子节点left(i),right(i),选出三者最大(或者最小)者,如果最大(小)值不是节点i而是它的一个孩子节点,那边交互节点i和该节点,然后再调用调整堆过程,这是一个递归的过程。调整堆的过程时间复杂度与堆的深度有关系,是lgn的操作,因为是沿着深度方向进行调整的。

堆排序:堆排序是利用上面的两个过程来进行的。首先是根据元素构建堆。然后将堆的根节点取出(一般是与最后一个节点进行交换),将前面len-1个节点继续进行堆调整的过程,然后再将根节点取出,这样一直到所有节点都取出。堆排序过程的时间复杂度是O(nlgn)。因为建堆的时间复杂度是O(n)(调用一次);调整堆的时间复杂度是lgn,调用了n-1次,所以堆排序的时间复杂度是O(nlgn) [2] 

注意

只需做n-1趟排序,选出较大的n-1关键字即可以使得文件递增有序。

用小根堆排序与利用大根堆类似,只不过其排序结果是递减有序的。堆排序和直接选择排序相反:在任何时刻堆排序中无序区总是在有序区之前,且有序区是在原向量的尾部由后往前逐步扩大至整个向量为止

特点

堆排序(HeapSort)是一树形选择排序。堆排序的特点是:在排序过程中,将R[l..n]看成是一棵完全二叉树顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系(参见二叉树的顺序存储结构),在当前无序区中选择关键字最大(或最小)的记录

算法分析

编辑

堆排序的时间,主要由建立初始堆和反复重建堆这两部分的时间开销构成,它们均是通过调用Heapify实现的。

平均性能

O(N*logN)

其他性能

由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。

堆排序是就地排序,辅助空间为O(1).

它是不稳定的排序方法。(排序的稳定性是指如果在排序的序列中,存在前后相同的两个元素的话,排序前 和排序后他们的相对位置不发生变化)

示例代码

编辑

C语言

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

#include <stdio.h>

void swap(int *a, int *b);

void adjustHeap(int param1,int j, int inNums[]);

void  HeapSort(int nums, int inNums[]);

//大根堆进行调整

void adjustHeap(int param1, int j, int inNums[])

{

    int temp=inNums[param1];

    for (int k=param1*2+1;k<j;k=k*2+1)

    {

        //如果右边值大于左边值,指向右边

        if (k+1<j && inNums[k]< inNums[k+1])

        {

            k++;

        }

        //如果子节点大于父节点,将子节点值赋给父节点,并以新的子节点作为父节点(不用进行交换)

        if (inNums[k]>temp)

        {

            inNums[param1]=inNums[k];

            param1=k;

        }

        else

            break;

    }

        //put the value in the final position

    inNums[param1]=temp;

}

//堆排序主要算法

void HeapSort(int nums,int inNums[])

{

    //1.构建大顶堆

    for (int i=nums/2-1;i>=0;i--)

    {

                //put the value in the final position

        adjustHeap(i,nums,inNums);

    }

    //2.调整堆结构+交换堆顶元素与末尾元素

    for (int j=nums-1;j>0;j--)

    {

                //堆顶元素和末尾元素进行交换

        int temp=inNums[0];

        inNums[0]=inNums[j];

        inNums[j]=temp;

        adjustHeap(0,j,inNums);//重新对堆进行调整

    }

}

int main() {

    int data[] = {6,5,8,4,7,9,1,3,2};

    int len = sizeof(data) / sizeof(int);

    HeapSort(len,data);

    return 0;

}

C++

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

3148

//堆排序

//整理节点time:O(lgn)

template<typenameT>

void MinHeapify(T*arry,int size,int element)

{

int lchild=element*2+1,rchild=lchild+1;//左右子树

while(rchild<size)//子树均在范围内

{

if(arry[element]<=arry[lchild]&&arry[element]<=arry[rchild])//如果比左右子树都小,完成整理

{

return;

}

if(arry[lchild]<=arry[rchild])//如果左边最小

{

swap(arry[element],arry[lchild]);//把左面的提到上面

element=lchild;//循环时整理子树

}

else//否则右面最小

{

swap(arry[element],arry[rchild]);//同理

element=rchild;

}

lchild=element*2+1;

rchild=lchild+1;//重新计算子树位置

}

if(lchild<size&&arry[lchild]<arry[element])//只有左子树且子树小于自己

{

swap(arry[lchild],arry[element]);

}

return;

}

//堆排序time:O(nlgn)

template<typenameT>

void HeapSort(T*arry,int size)

{

int i;

for(i=size-1;i>=0;i--)//从子树开始整理树

{

MinHeapify(arry,size,i);

}

while(size>0)//拆除树

{

swap(arry[size-1],arry[0]);//将根(最小)与数组最末交换

size--;//树大小减小

MinHeapify(arry,size,0);//整理树

}

return;

}

Java

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

106

107

108

109

110

111

112

113

public class HeapSort{

private static int[] sort=new int[]{1,0,10,20,3,5,6,4,9,8,12,

17,34,11};

public static void main(String[] args){

buildMaxHeapify(sort);

heapSort(sort);

print(sort);

}

private static void buildMaxHeapify(int[] data){

//没有子节点的才需要创建最大堆,从最后一个的父节点开始

int startIndex=getParentIndex(data.length-1);

//从尾端开始创建最大堆,每次都是正确的堆

for(int i=startIndex;i>=0;i--){

maxHeapify(data,data.length,i);

}

}

/**

*创建最大堆

*

*@paramdata

*@paramheapSize需要创建最大堆的大小,一般在sort的时候用到,因为最多值放在末尾,末尾就不再归入最大堆了

*@paramindex当前需要创建最大堆的位置

*/

private static void maxHeapify(int[] data,int heapSize,int index){

//当前点与左右子节点比较

int left=getChildLeftIndex(index);

int right=getChildRightIndex(index);

int largest=index;

if(left<heapSize&&data[index]<data[left]){

largest=left;

}

if(right<heapSize&&data[largest]<data[right]){

largest=right;

}

//得到最大值后可能需要交换,如果交换了,其子节点可能就不是最大堆了,需要重新调整

if(largest!=index){

int temp=data[index];

data[index]=data[largest];

data[largest]=temp;

maxHeapify(data,heapSize,largest);

}

}

/**

*排序,最大值放在末尾,data虽然是最大堆,在排序后就成了递增的

*

*@paramdata

*/

private static void heapSort(int[] data){

//末尾与头交换,交换后调整最大堆

for(int i=data.length-1;i>0;i--){

int temp=data[0];

data[0]=data[i];

data[i]=temp;

maxHeapify(data,i,0);

}

}

/**

*父节点位置

*

*@paramcurrent

*@return

*/

private static int getParentIndex(int current){

return(current-1)>>1;

}

/**

*左子节点position注意括号,加法优先级更高

*

*@paramcurrent

*@return

*/

private static int getChildLeftIndex(int current){

return(current<<1)+1;

}

/**

*右子节点position

*

*@paramcurrent

*@return

*/

private static int getChildRightIndex(int current){

return(current<<1)+2;

}

private static void print(int[] data){

int pre=-2;

for(int i=0;i<data.length;i++){

if(pre<(int)getLog(i+1)){

pre=(int)getLog(i+1);

System.out.println();

}

System.out.print(data[i]+"|");

}

}

/**

*以2为底的对数

*

*@paramparam

*@return

*/

private static double getLog(double param){

return Math.log(param)/Math.log(2);

}

}

Python

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

#!/usr/bin/envpython

#-*-coding:utf-8-*-

def heap_sort(lst):

for startinrange((len(lst)-2)/2,-1,-1):

sift_down(lst,start,len(lst)-1)

for endinrange(len(lst)-1,0,-1):

lst[0],lst[end]=lst[end],lst[0]

sift_down(lst,0,end-1)

return lst

def sift_down(lst,start,end):

root=start

while True:

child=2*root+1

if child>end:

break

if child+1<=end and lst[child]<lst[child+1]:

child+=1

if lst[root]<lst[child]:

lst[root],lst[child]=lst[child],lst[root]

root=child

else:

break

def main():

l=[9,2,1,7,6,8,5,3,4]

heap_sort(l)

if__name__=="__main__":

main()

C#

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

4

///<summary>

///构建堆

///</summary>

static void HeapAdjust(List<int> list,int parent,int length)

{

int temp=list[parent];

int child=2*parent+1;

while(child<length)

{

if(child+1<length&&list[child]<list[child+1])child++;

if(temp>=list[child])

break;

list[parent]=list[child];

parent=child;

child=2*parent+1;

}

list[parent]=temp;

}

///<summary>

///堆排序

///</summary>

public static List<int> HeapSort(List<int> list,int top)

{

List<int> topNode=new List<int>();

for(int i=list.Count/2-1;i>=0;i--)

{

HeapAdjust(list,i,list.Count);

}

for(int i=list.Count-1;i>=list.Count-top;i--)

{

int temp=list[0];

list[0]=list[i];

list[i]=temp;

topNode.Add(temp);

HeapAdjust(list,0,i);

}

return topNode;

}

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

47

/**

*AuthorJenaszhang

*/

Array.prototype.buildMaxHeap=function(){

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

this.heapAdjust(i,this.length);

}

};

Array.prototype.swap=function(i,j){

var tmp=this[i];

this[i]=this[j];

this[j]=tmp;

};

Array.prototype.heapSort=function(){

this.buildMaxHeap();

for(vari=this.length-1;i>0;i--){

this.swap(0,i);

this.heapAdjust(0,i);

}

return this;

};

Array.prototype.heapAdjust=function(i,j){

var largest=i;

var left=2*i+1;

var right=2*i+2;

if(left<j&&this[largest]<this[left]){

largest=left;

}

if(right<j&&this[largest]<this[right]){

largest=right;

}

if(largest!=i){

this.swap(i,largest);

this.heapAdjust(largest,j);

}

};

var a=new Array();

[].push.apply(a,[2,3,89,57,23,72,43,105]);

console.log(a.heapSort());

Pascal代码

示例1

const max=100000;

var

a:array[0..max] of longint;

n,i,tot,t:longint;

procedure down(i:longint);

var j,t:longint;

begin

while i<=tot shr 1 do

begin

j:=2*i;

if (j<tot) and (a[j+1]<a[j]) then inc(j);

if a[i]>a[j] then

begin

t:=a[i];

a[i]:=a[j];

a[j]:=t;

i:=j;

end

else break;

end;

end;

begin

readln(n);

for i:=1 to n do read(a[i]);

tot:=n;

for i:=n shr 1 downto 1 do down(i);

for i:=1 to n do

begin

t:=a[1]; a[1]:=a[tot]; a[tot]:=t;

dec(tot);

down(1);

end;

for i:=n downto 1 do write(a[i],' ');

end.

示例2:

const max=100000;

var

a:array[0..max] of longint;

n,i,tot,t:longint;

procedure down(i:longint);

var j,t:longint;

begin

while i<=tot shr 1 do

begin

j:=2*i;

if (j<tot) and (a[j+1]<a[j]) then inc(j);

if a[i]>a[j] then

begin

t:=a[i];

a[i]:=a[j];

a[j]:=t;

i:=j;

end

else break;

end;

end;

procedure up(i:longint);

var j,t:longint;

begin

j:=i;

while (j>1) and (a[j]<a[j shr 1]) do

begin

t:=a[j];

a[j]:=a[j shr 1];

a[j shr 1]:=t;

j:=j shr 1;

end;

end;

begin

readln(n);

for i:=1 to n do

begin

read(a[i]);

up(i);

end;

tot:=n;

for i:=1 to n do

begin

t:=a[1]; a[1]:=a[tot]; a[tot]:=t;

dec(tot);

down(1);

end;

for i:=n downto 1 do write(a[i],' ');

end.

原地排序

 希尔排序

 冒泡排序

 插入排序

 选择排序

 堆排序

参考资料

· 1.堆排序 .维基百科[引用日期2013-03-30]

· 2.堆排序 csdn2010-10-28[引用日期2015-01-21]

词条标签:

中国电子学会 , 科学

快速排序

快速排序(Quicksort)是对冒泡排序的一种改进。

快速排序由C. A. R. Hoare1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

中文名

快速排序算法

外文名

quick sort

    

快速排序

提出者

C. A. R. Hoare

提出时间

1962

应用学科

计算机科学

适用领域范围

Pascalc++等语言

目录

00001. 1 算法介绍

00002. 2 排序演示

00003.  示例

00004.  调用函数

00005. 3 示例代码

00006.  GO

00007.  Ruby

00008.  Erlang语言

00001.  Haskell语言

00002.  C++语言

00003.  C语言版本

00004.  JavaScript

00005.  Java

00006.  C#

00007.  F#

00008.  PHP

00009.  Pascal

00001.  Python递归

00002. 4 优化

00003.  三平均分区法

00004.  根据分区大小调整算法

00005.  不同的分区方案考虑

00006.  并行的快速排序

00007. 5 变种

00008.  随机化快排

00001.  平衡快排

00002.  外部快排

00003.  三路基数快排

00004. 6 伪代码

00005.  非随机

00006.  随机

00007.  性能分析

算法介绍

快排图

设要排序的数组A[0]……A[N-1],首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。值得注意的是,快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动。

一趟快速排序的算法是:

1)设置两个变量ij排序开始的时候:i=0j=N-1

2)以第一个数组元素作为关键数据,赋值给key,即key=A[0]

3)从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于key的值A[j],将A[j]A[i]互换;

4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于keyA[i],将A[i]A[j]互换;

5)重复第34步,直到i=j; (3,4步中,没找到符合条件的值,即3A[j]不小于key,4A[i]不大于key的时候改变ji的值,使得j=j-1i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+j-完成的时候,此时令循环结束)。

排序演示

示例

假设用户输入了如下数组:

下标

0

1

2

3

4

5

数据

6

2

7

3

8

9

创建变量i=0(指向第一个数据), j=5(指向最后一个数据), k=6(赋值为第一个数据的值)

我们要把所有比k小的数移动到k的左面,所以我们可以开始寻找比6小的数,从j开始,从右往左找,不断递减变量j的值,我们找到第一个下标3的数据比6小,于是把数据3移到下标0的位置,把下标0的数据6移到下标3,完成第一次比较:

下标

0

1

2

3

4

5

数据

3

2

7

6

8

9

i=0 j=3 k=6

接着,开始第二次比较,这次要变成找比k大的了,而且要从前往后找了。递加变量i,发现下标2的数据是第一个比k大的,于是用下标2的数据7j指向的下标3的数据的6做交换,数据状态变成下表:

下标

0

1

2

3

4

5

数据

3

2

6

7

8

9

i=2 j=3 k=6

称上面两次比较为一个循环。

接着,再递减变量j,不断重复进行上面的循环比较。

在本例中,我们进行一次循环,就发现ij“碰头了:他们都指向了下标2。于是,第一遍比较结束。得到结果如下,凡是k(=6)左边的数都比它小,凡是k右边的数都比它大:

下标

0

1

2

3

4

5

数据

3

2

6

7

8

9

如果ij没有碰头的话,就递加i找大的,还没有,就再递减j找小的,如此反复,不断循环。注意判断和寻找是同时进行的。

然后,对k两边的数据,再分组分别进行上述的过程,直到不能再分组为止。

注意:第一遍快速排序不会直接得到最终结果,只会把比k大和比k小的数分到k的两边。为了得到最后结果,需要再次对下标2两边的数组分别执行此步骤,然后再分解数组,直到数组不能再分解为止(只有一个数据),才能得到正确结果。

调用函数

c++中可以用函数qsort()可以直接为数组进行排序。 [1] 

 :

void qsort(void *base, int nelem, int width, int (*fcmp)(const void *,const void *));

参数:
排序数组首地址
数组中待排序元素数量
各元素的占用空间大小
指向函数的指针,用于确定排序的顺序

示例代码

GO

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

// 第一种写法

func quickSort(values []int, left, right int) {

    temp := values[left]

    p := left

    i, j := left, right

    for i <= j {

        for j >= p && values[j] >= temp {

            j--

        }

        if j >= p {

            values[p] = values[j]

            p = j

        }

        if values[i] <= temp && i <= p {

            i++

        }

        if i <= p {

            values[p] = values[i]

            p = i

        }

    }

    values[p] = temp

    if p-left > 1 {

        quickSort(values, left, p-1)

    }

    if right-p > 1 {

        quickSort(values, p+1, right)

    }

}

func QuickSort(values []int) {

    if len(values) <= 1 {

        return

    }

    quickSort(values, 0, len(values)-1)

}

// 第二种写法

func Quick2Sort(values []int) {

    if len(values) <= 1 {

        return

    }

    mid, i := values[0], 1

    head, tail := 0, len(values)-1

    for head < tail {

        fmt.Println(values)

        if values[i] > mid {

            values[i], values[tail] = values[tail], values[i]

            tail--

        else {

            values[i], values[head] = values[head], values[i]

            head++

            i++

        }

    }

    values[head] = mid

    Quick2Sort(values[:head])

    Quick2Sort(values[head+1:])

}

Ruby

1

2

3

def quick_sort(a)  

  (x=a.pop) ? quick_sort(a.select { |i| i <= x }) + [x] + quick_sort(a.select { |i| i > x }) : []

end

Erlang语言

1

2

3

4

5

6

超简短实现:

q_sort([])->

[];

q_sort([H|R])->

q_sort([X||X<-R,X<H])++[H]++

q_sort([X||X<-R,X>=H]).

Haskell语言

1

2

3

q_sort n=case of

    []->[]

    (x:xs)->q_sort [a|a<-xs,a<=x]++[x]++q_sort [a|a<-xs,a>x]

C++语言

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

#include <iostream>

using namespace std;

void Qsort(int a[], int low, int high)

{

    if(low >= high)

    {

        return;

    }

    int first = low;

    int last = high;

    int key = a[first];/*用字表的第一个记录作为枢轴*/

    while(first < last)

    {

        while(first < last && a[last] >= key)

        {

            --last;

        }

        a[first] = a[last];/*将比第一个小的移到低端*/

        while(first < last && a[first] <= key)

        {

            ++first;

        }

         

        a[last] = a[first];    

/*将比第一个大的移到高端*/

    }

    a[first] = key;/*枢轴记录到位*/

    Qsort(a, low, first-1);

    Qsort(a, first+1, high);

}

int main()

{

    int a[] = {57, 68, 59, 52, 72, 28, 96, 33, 24};

    Qsort(a, 0, sizeof(a) / sizeof(a[0]) - 1);/*这里原文第三个参数要减1否则内存越界*/

    for(int i = 0; i < sizeof(a) / sizeof(a[0]); i++)

    {

        cout << a[i] << "";

    }

     

    return 0;

}/*参考数据结构p274(清华大学出版社,严蔚敏)*/

C语言版本

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

void sort(int *a, int left, int right)

{

    if(left >= right)/*如果左边索引大于或者等于右边的索引就代表已经整理完成一个组了*/

    {

        return ;

    }

    int i = left;

    int j = right;

    int key = a[left];

     

    while(i < j)                               /*控制在当组内寻找一遍*/

    {

        while(i < j && key <= a[j])

        /*而寻找结束的条件就是,1,找到一个小于或者大于key的数(大于或小于取决于你想升

        序还是降序)2,没有符合条件1的,并且i与j的大小没有反转*/ 

        {

            j--;/*向前寻找*/

        }

         

        a[i] = a[j];

        /*找到一个这样的数后就把它赋给前面的被拿走的i的值(如果第一次循环且key是

        a[left],那么就是给key)*/

         

        while(i < j && key >= a[i])

        /*这是i在当组内向前寻找,同上,不过注意与key的大小关系停止循环和上面相反,

        因为排序思想是把数往两边扔,所以左右两边的数大小与key的关系相反*/

        {

            i++;

        }

         

        a[j] = a[i];

    }

     

    a[i] = key;/*当在当组内找完一遍以后就把中间数key回归*/

    sort(a, left, i - 1);/*最后用同样的方式对分出来的左边的小组进行同上的做法*/

    sort(a, i + 1, right);/*用同样的方式对分出来的右边的小组进行同上的做法*/

                       /*当然最后可能会出现很多分左右,直到每一组的i = j 为止*/

}

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

function quickSort(array){

function sort(prev, numsize){

var nonius = prev;

var j = numsize -1;

var flag = array[prev];

if ((numsize - prev) > 1) {

while(nonius < j){

for(; nonius < j; j--){

if (array[j] < flag) {

array[nonius++] = array[j]; //a[i] = a[j]; i += 1;

break;

};

}

for( ; nonius < j; nonius++){

if (array[nonius] > flag){

array[j--] = array[nonius];

break;

}

}

}

array[nonius] = flag;

sort(0, nonius);

sort(nonius + 1, numsize);

}

}

sort(0, array.length);

return array;

}

Java

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

106

107

108

109

110

111

112

113

114

115

116

117

 

class Quick

{

publicvoid sort(int arr[],int low,int high)

 {

int l=low;

int h=high;

int povit=arr[low];

while(l<h)

 {

while(l<h&&arr[h]>=povit)

 h--;

if(l<h){

int temp=arr[h];

 arr[h]=arr[l];

 arr[l]=temp;

 l++;

 }

while(l<h&&arr[l]<=povit)

 l++;

if(l<h){

int temp=arr[h];

 arr[h]=arr[l];

 arr[l]=temp;

 h--;

 }

 }

 print(arr);

 System.out.print("l="+(l+1)+"h="+(h+1)+"povit="+povit+"\n");

if(l>low)sort(arr,low,l-1);

if(h<high)sort(arr,l+1,high);

 }

}

/*//////////////////////////方式二////////////////////////////////*/

更高效点的代码:

public<TextendsComparable<?superT>>

T[]quickSort(T[]targetArr,intstart,intend)

{

inti=start+1,j=end;

Tkey=targetArr[start];

SortUtil<T>sUtil=newSortUtil<T>();

if(start>=end)return(targetArr);

/*从i++和j--两个方向搜索不满足条件的值并交换

*

*条件为:i++方向小于key,j--方向大于key

*/

while(true)

{

while(targetArr[j].compareTo(key)>0)j--;

while(targetArr[i].compareTo(key)<0&&i<j)i++;

if(i>=j)break;

sUtil.swap(targetArr,i,j);

if(targetArr[i]==key)

{

j--;

}else{

i++;

}

}

/*关键数据放到‘中间’*/

sUtil.swap(targetArr,start,j);

if(start<i-1)

{

this.quickSort(targetArr,start,i-1);

}

if(j+1<end)

{

this.quickSort(targetArr,j+1,end);

}

returntargetArr;

}

/*//////////////方式三:减少交换次数,提高效率/////////////////////*/

private<TextendsComparable<?superT>>

voidquickSort(T[]targetArr,intstart,intend)

{

inti=start,j=end;

Tkey=targetArr[start];

while(i<j)

{

/*按j--方向遍历目标数组,直到比key小的值为止*/

while(j>i&&targetArr[j].compareTo(key)>=0)

{

j--;

}

if(i<j)

{

/*targetArr[i]已经保存在key中,可将后面的数填入*/

targetArr[i]=targetArr[j];

i++;

}

/*按i++方向遍历目标数组,直到比key大的值为止*/

while(i<j&&targetArr[i].compareTo(key)<=0)

/*此处一定要小于等于零,假设数组之内有一亿个1,0交替出现的话,而key的值又恰巧是1的话,那么这个小于等于的作用就会使下面的if语句少执行一亿次。*/

{

i++;

}

if(i<j)

{

/*targetArr[j]已保存在targetArr[i]中,可将前面的值填入*/

targetArr[j]=targetArr[i];

j--;

}

}

/*此时i==j*/

targetArr[i]=key;

/*递归调用,把key前面的完成排序*/

this.quickSort(targetArr,start,i-1);

/*递归调用,把key后面的完成排序*/

this.quickSort(targetArr,j+1,end);

}

C#

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

578

    using System; 

    using System.Collections.Generic; 

    using System.Linq; 

    using System.Text;

    namespace test

{

    class QuickSort

    {

        static void Main(string[] args)

        {

            int[] array = { 49, 38, 65, 97, 76, 13, 27 };

            sort(array, 0, array.Length - 1);

            Console.ReadLine();

        }

        /**一次排序单元,完成此方法,key左边都比key小,key右边都比key大。

          

**@param array排序数组 

          

**@param low排序起始位置 

          

**@param high排序结束位置

          

**@return单元排序后的数组 */

        private static int sortUnit(int[] array, int low, int high)

        {

            int key = array[low];

            while (low < high)

            {

                /*从后向前搜索比key小的值*/

                while (array[high] >= key && high > low)

                    --high; 

                /*比key小的放左边*/

                array[low] = array[high];   

                /*从前向后搜索比key大的值,比key大的放右边*/

                while (array[low] <= key && high > low)

                    ++low; 

                /*比key大的放右边*/

                array[high] = array[low];

            }

            /*左边都比key小,右边都比key大。//将key放在游标当前位置。//此时low等于high */

            array[low] = key;

            foreach (int in array)

            {

                Console.Write("{0}\t", i);

            }

            Console.WriteLine();

            return high;

        }    

        /**快速排序 

*@paramarry 

*@return */

        public static void sort(int[] array, int low, int high)

        {

            if (low >= high)

                return

            /*完成一次单元排序*/

            int index = sortUnit(array, low, high); 

            /*对左边单元进行排序*/

            sort(array, low, index - 1);

            /*对右边单元进行排序*/

            sort(array, index + 1, high);

        }

    }

运行结果:27 38 13 49 76 97 65

13 27 38 49 76 97 65
13 27 38 49 65 76 97

快速排序就是递归调用此过程——在以49为中点分割这个数据序列,分别对前面一部分和后面一部分进行类似的快速排序,从而完成全部数据序列的快速排序,最后把此数据序列变成一个有序的序列,根据这种思想对于上述数组A的快速排序的全过程如图6所示:

初始状态 {49 38 65 97 76 13 27} 进行一次快速排序之后划分为 {27 38 13} 49 {76 97 65} 分别对前后两部分进行快速排序{27 38 13} 经第三步和第四步交换后变成 {13 27 38} 完成排序。{76 97 65} 经第三步和第四步交换后变成 {65 76 97} 完成排序。图示

F#

1

2

3

4

5

6

let rec qsort = 

    function

    [] -> []

    |x::xs ->

        qsort [for i in xs do if i < x then yield i]@

        x::qsort [for i in xs do if i >= x then yield i]

PHP

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

<?php

function quickSort(&$arr){

    if(count($arr)>1){

        $k=$arr[0];

        $x=array();

        $y=array();

        $_size=count($arr);

        for($i=1;$i<$_size;$i++){

            if($arr[$i]<=$k){

                $x[]=$arr[$i];

            }elseif($arr[$i]>$k){

                $y[]=$arr[$i];

            }

        }

        $x=quickSort($x);

        $y=quickSort($y);

        return array_merge($x,array($k),$y);

    }else{

        return$arr;

    }

}

?>

Pascal

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

这里是完全程序,过程部分为快排

program qsort;

var n,p:integer;

    a:array[0..100000of integer;

procedure qs(l,r:integer);//假设被排序的数组是a,且快排后按升序排列)

var i,j,m,t:integer;

begin

  i:=l;

  j:=r;//(l(left),r(right)表示快排的左右区间)

  m:=a[(l+r)div2];//注意:本句不能写成:m:=(l+r)div2;

  repeat

  while a[i]<m do inc(i);

  while a[j]>m do dec(j);//若是降序把'<'与‘>'互换;

  if i<=j then

    begin

    t:=a[i];

    a[i]:=a[j];

    a[j]:=t;

    inc(i);

    dec(j);

    end;

  until i>j;

  if l<j then qs(l,j);//递归查找左区间

  if i<r then qs(i,r);//递归查找右区间

end;

begin

  readln(n);//有n个数据要处理

  for p:=1 to do read(a[p]);//输入数据

  qs(1,n);

  for p:=1 to do write(a[p],'');//输出快排后的数据

end.

或者

procedure quickSort(var a:array of integer;l,r:Integer);

var i,j,x:integer;

begin

  if l>=r then exit;

  i:=l;

  j:=r;

  x:=a[i];

  while i<=j do 

    begin

    while (i<j)and(a[j]>x) do dec(j);

    if i<j then 

      begin

      a[i]:=a[j];

      inc(i);

      end;

    while (i<j)and(a[i]<x) do inc(i);

    if i<j then 

      begin

      a[j]:=a[i];

      dec(j);

      end;

    a[i]:=x;

    quicksort(a,l,i-1);

    quicksort(a,i+1,r);

    end;

end;

Python递归

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

#quick sort

def quickSort(L, low, high):

    i = low 

    j = high

    if i >= j:

        return L

    key = L[i]

    while i < j:

        while i < j and L[j] >= key:

            j = j-1                                                             

        L[i] = L[j]

        while i < j and L[i] <= key:    

            i = i+1 

        L[j] = L[i]

    L[i] = key 

    quickSort(L, low, i-1)

    quickSort(L, j+1, high)

    return L

优化

三平均分区法

关于这一改进的最简单的描述大概是这样的:与一般的快速排序方法不同,它并不是选择待排数组的第一个数作为中轴,而是选用待排数组最左边、最右边和最中间的三个元素的中间值作为中轴。这一改进对于原来的快速排序算法来说,主要有两点优势:
  (1) 首先,它使得最坏情况发生的几率减小了。
  (2) 其次,未改进的快速排序算法为了防止比较时数组越界,在最后要设置一个哨点。

根据分区大小调整算法

这一方面的改进是针对快速排序算法的弱点进行的。快速排序对于小规模的数据集性能不是很好。可能有人认为可以忽略这个缺点不计,因为大多数排序都只要考虑大规模的适应性就行了。但是快速排序算法使用了分治技术,最终来说大的数据集都要分为小的数据集来进行处理。由此可以得到的改进就是,当数据集较小时,不必继续递归调用快速排序算法,而改为调用其他的对于小规模数据集处理能力较强的排序算法来完成。Introsort就是这样的一种算法,它开始采用快速排序算法进行排序,当递归达到一定深度时就改为堆排序来处理。这样就克服了快速排序在小规模数据集处理中复杂的中轴选择,也确保了堆排序在最坏情况下O(n log n)的复杂度。
  另一种优化改进是当分区的规模达到一定小时,便停止快速排序算法。也即快速排序算法的最终产物是一个几乎排序完成的有序数列。数列中有部分元素并没有排到最终的有序序列的位置上,但是这种元素并不多。可以对这种几乎完成排序的数列使用插入排序算法进行排序以最终完成整个排序过程。因为插入排序对于这种几乎完成的排序数列有着接近线性的复杂度。这一改进被证明比持续使用快速排序算法要有效的多。
  另一种快速排序的改进策略是在递归排序子分区的时候,总是选择优先排序那个最小的分区。这个选择能够更加有效的利用存储空间从而从整体上加速算法的执行。

不同的分区方案考虑

对于快速排序算法来说,实际上大量的时间都消耗在了分区上面,因此一个好的分区实现是非常重要的。尤其是当要分区的所有的元素值都相等时,一般的快速排序算法就陷入了最坏的一种情况,也即反复的交换相同的元素并返回最差的中轴值。无论是任何数据集,只要它们中包含了很多相同的元素的话,这都是一个严重的问题,因为许多底层的分区都会变得完全一样。
  对于这种情况的一种改进办法就是将分区分为三块而不是原来的两块:一块是小于中轴值的所有元素,一块是等于中轴值的所有元素,另一块是大于中轴值的所有元素。另一种简单的改进方法是,当分区完成后,如果发现最左和最右两个元素值相等的话就避免递归调用而采用其他的排序算法来完成。

并行的快速排序

由于快速排序算法是采用分治技术来进行实现的,这就使得它很容易能够在多台处理机上并行处理。
  在大多数情况下,创建一个线程所需要的时间要远远大于两个元素比较和交换的时间,因此,快速排序的并行算法不可能为每个分区都创建一个新的线程。一般来说,会在实现代码中设定一个阀值,如果分区的元素数目多于该阀值的话,就创建一个新的线程来处理这个分区的排序,否则的话就进行递归调用来排序。
  对于这一并行快速排序算法也有其改进。该算法的主要问题在于,分区的这一步骤总是要在子序列并行处理之前完成,这就限制了整个算法的并行程度。解决方法就是将分区这一步骤也并行处理。改进后的并行快速排序算法使用2n个指针来并行处理分区这一步骤,从而增加算法的并行程度。

变种

随机化快排

快速排序的最坏情况基于每次划分对主元的选择。基本的快速排序选取第一个元素作为主元。这样在数组已经有序的情况下,每次划分将得到最坏的结果。一种比较常见的优化方法是随机化算法,即随机选取一个元素作为主元。这种情况下虽然最坏情况仍然是O(n^2),但最坏情况不再依赖于输入数据,而是由于随机函数取值不佳。实际上,随机化快速排序得到理论最坏情况的可能性仅为1/(2^n)。所以随机化快速排序可以对于绝大多数输入数据达到O(nlogn)的期望时间复杂度。一位前辈做出了一个精辟的总结:随机化快速排序可以满足一个人一辈子的人品需求。

随机化快速排序的唯一缺点在于,一旦输入数据中有很多的相同数据,随机化的效果将直接减弱。对于极限情况,即对于n个相同的数排序,随机化快速排序的时间复杂度将毫无疑问的降低到O(n^2)。解决方法是用一种方法进行扫描,使没有交换的情况下主元保留在原位置。

平衡快排

每次尽可能地选择一个能够代表中值的元素作为关键数据,然后遵循普通快排的原则进行比较、替换和递归。通常来说,选择这个数据的方法是取开头、结尾、中间3个数据,通过比较选出其中的中值。取这3个值的好处是在实际问题中,出现近似顺序数据或逆序数据的概率较大,此时中间数据必然成为中值,而也是事实上的近似中值。万一遇到正好中间大两边小(或反之)的数据,取的值都接近最值,那么由于至少能将两部分分开,实际效率也会有2倍左右的增加,而且利于将数据略微打乱,破坏退化的结构。

外部快排

与普通快排不同的是,关键数据是一段buffer,首先将之前和之后的M/2个元素读入buffer并对该buffer中的这些元素进行排序,然后从被排序数组的开头(或者结尾)读入下一个元素,假如这个元素小于buffer中最小的元素,把它写到最开头的空位上;假如这个元素大于buffer中最大的元素,则写到最后的空位上;否则把buffer中最大或者最小的元素写入数组,并把这个元素放在buffer里。保持最大值低于这些关键数据,最小值高于这些关键数据,从而避免对已经有序的中间的数据进行重排。完成后,数组的中间空位必然空出,把这个buffer写入数组中间空位。然后递归地对外部更小的部分,循环地对其他部分进行排序。

三路基数快排

Three-way Radix Quicksort,也称作Multikey QuicksortMulti-key Quicksort):结合了基数排序radix sort,如一般的字符串比较排序就是基数排序)和快排的特点,是字符串排序中比较高效的算法。该算法被排序数组的元素具有一个特点,即multikey,如一个字符串,每个字母可以看作是一个key。算法每次在被排序数组中任意选择一个元素作为关键数据,首先仅考虑这个元素的第一个key(字母),然后把其他元素通过key的比较分成小于、等于、大于关键数据的三个部分。然后递归地基于这一个key位置对小于大于部分进行排序,基于下一个key等于部分进行排序。

伪代码

非随机

QUICKSORT(Apr)

1if p<r

2then q ←PARTITION(Apr)

3QUICKSORT(Apq-1)

4QUICKSORT(Aq+1r)

为排序一个完整的数组A,最初的调用是QUICKSORT(A1length[A])。

快速排序算法的关键是PARTITION过程,它对子数组A[p..r]进行就地重排:

PARTITION(Apr)

1xA[r]

2ip-1

3for jto r-1

4do if A[j]≤x

5then ii+1

6exchange A[i]←→A[j]

7exchange A[i+1]←→A[r]

8return i+1 [2] 

随机

PARTITIONQUICKSORT所作的改动比较小。在新的划分过程中,我们在真正进行划分之前实现交换:

(其中PARTITION过程同快速排序伪代码(非随机))

RANDOMIZED-PARTITION(Apr)

1i← RANDOM(pr)

2exchange A[r]←→A[i]

3return PARTITION(Apr)

新的快速排序过程不再调用PARTITION,而是调用RANDOMIZED-PARTITION

RANDOMIZED-QUICKSORT(Apr)

1if p<r

2then q← RANDOMIZED-PARTITION(Apr)

3RANDOMIZED-QUICKSORT(Apq-1)

4RANDOMIZED-QUICKSORT(Aq+1r) [2] 

性能分析

这里为方便起见,我们假设算法Quick_Sort的范围阈值为1(即一直将线性表分解到只剩一个元素),这对该算法复杂性的分析没有本质的影响。

我们先分析函数partition的性能,该函数对于确定的输入复杂性是确定的。观察该函数,我们发现,对于有n个元素的确定输入L[p..r],该函数运行时间显然为θn)。

最坏情况

无论适用哪一种方法来选择pivot,由于我们不知道各个元素间的相对大小关系(若知道就已经排好序了),所以我们无法确定pivot的选择对划分造成的影响。因此对各种pivot选择法而言,最坏情况和最好情况都是相同的。

我们从直觉上可以判断出最坏情况发生在每次划分过程产生的两个区间分别包含n-1个元素和1个元素的时候(设输入的表有n个元素)。下面我们暂时认为该猜测正确,在后文我们再详细证明该猜测。

对于有n个元素的表L[p..r],由于函数Partition的计算时间为θn),所以快速排序在序坏情况下的复杂性有递归式如下:

T(1)=θ(1),T(n)=T(n-1)+T(1)+θ(n) (1)

用迭代法可以解出上式的解为T(n)=θn2)。

这个最坏情况运行时间与插入排序是一样的。

下面我们来证明这种每次划分过程产生的两个区间分别包含n-1个元素和1个元素的情况就是最坏情况。

T(n)是过程Quick_Sort作用于规模为n的输入上的最坏情况的时间,则

T(n)=max(T(q)+T(n-q))+θn),其中1≤q≤n-1 (2)

我们假设对于任何k<n,总有T(k≤ck,其中c为常数;显然当k=1时是成立的。

将归纳假设代入(2),得到:

T(n≤max(cq2+c(n-q)2)+θn)=c*max(q2+(n-q)2)+θn)

因为在[1,n-1]q2+(n-q)2关于q递减,所以当q=1q2+(n-q)2有最大值n2-2(n-1)。于是有:

T(n≤cn2-2c(n-1)+θn≤cn2

只要c足够大,上面的第二个小于等于号就可以成立。于是对于所有的n都有T(n≤cn

这样,排序算法的最坏情况运行时间为θn2),且最坏情况发生在每次划分过程产生的两个区间分别包含n-1个元素和1个元素的时候。

时间复杂度on2)。

最好情况

如果每次划分过程产生的区间大小都为n/2,则快速排序法运行就快得多了。这时有:

T(n)=2T(n/2)+θn),T(1)=θ1) (3)

解得:T(n)=θnlogn)

快速排序法最佳情况下执行过程的递归树如下图所示,图中lgn表示以10为底的对数,而本文中用logn表示以2为底的对数.

由于快速排序法也是基于比较的排序法,其运行时间为Ωnlogn),所以如果每次划分过程产生的区间大小都为n/2,则运行时间θnlogn)就是最好情况运行时间。

但是,是否一定要每次平均划分才能达到最好情况呢?要理解这一点就必须理解对称性是如何在描述运行时间的递归式中反映的。我们假设每次划分过程都产生9:1的划分,乍一看该划分很不对称。我们可以得到递归式:

T(n)=T(n/10)+T(9n/10)+θn),T(1)=θ1) (4)

请注意树的每一层都有代价n,直到在深度log10n=θlogn)处达到边界条件,以后各层代价至多为n。递归于深度log10/9n=θlogn)处结束。这样,快速排序的总时间代价为T(n)=θnlogn),从渐进意义上看就和划分是在中间进行的一样。事实上,即使是99:1的划分时间代价也为θnlogn)。其原因在于,任何一种按常数比例进行划分所产生的递归树的深度都为θnlogn),其中每一层的代价为O(n),因而不管常数比例是什么,总的运行时间都为θnlogn),只不过其中隐含的常数因子有所不同。(关于算法复杂性的渐进阶,请参阅算法的复杂性)

平均情况

快速排序的平均运行时间为θ(nlogn)

我们对平均情况下的性能作直觉上的分析。

要想对快速排序的平均情况有个较为清楚的概念,我们就要对遇到的各种输入作个假设。通常都假设输入数据的所有排列都是等可能的。后文中我们要讨论这个假设。

当我们对一个随机的输入数组应用快速排序时,要想在每一层上都有同样的划分是不太可能的。我们所能期望的是某些划分较对称,另一些则很不对称。事实上,我们可以证明,如果选择L[p..r]的第一个元素作为支点元素,Partition所产生的划分80%以上都比9:1更对称,而另20%则比9:1差,这里证明从略。

平均情况下,Partition产生的划分中既有好的,又有差的。这时,与Partition执行过程对应的递归树中,好、差划分是随机地分布在树的各层上的。为与我们的直觉相一致,假设好、差划分交替出现在树的各层上,且好的划分是最佳情况划分,而差的划分是最坏情况下的划分。在根节点处,划分的代价为n,划分出来的两个子表的大小为n-11,即最坏情况。在根的下一层,大小为n-1的子表按最佳情况划分成大小各为(n-1)/2的两个子表。这儿我们假设含1个元素的子表的边界条件代价为1

在一个差的划分后接一个好的划分后,产生出三个子表,大小各为1,(n-1)/2和(n-1)/2,代价共为2n-1=θn)。一层划分就产生出大小为(n-1)/2+1和(n-1)/2的两个子表,代价为n=θn)。这种划分差不多是完全对称的,比9:1的划分要好。从直觉上看,差的划分的代价θn)可被吸收到好的划分的代价θn)中去,结果是一个好的划分。这样,当好、差划分交替分布划分都是好的一样:仍是θnlogn),但θ记号中隐含的常数因子要略大一些。关于平均情况的严格分析将在后文给出。

在前文从直觉上探讨快速排序的平均性态过程中,我们已假定输入数据的所有排列都是等可能的。如果输入的分布满足这个假设时,快速排序是对足够大的输入的理想选择。但在实际应用中,这个假设就不会总是成立。

解决的方法是,利用随机化策略,能够克服分布的等可能性假设所带来的问题。

一种随机化策略是:与对输入的分布作假设不同的是对输入的分布作规定。具体地说,在排序输入的线性表前,对其元素加以随机排列,以强制的方法使每种排列满足等可能性。事实上,我们可以找到一个能在O(n)时间内对含n个元素的数组加以随机排列的算法。这种修改不改变算法的最坏情况运行时间,但它却使得运行时间能够独立于输入数据已排序的情况。

另一种随机化策略是:利用前文介绍的选择支点元素pivot的第四种方法,即随机地在L[p..r]中选择一个元素作为支点元素pivot。实际应用中通常采用这种方法。

快速排序的随机化版本有一个和其他随机化算法一样的有趣性质:没有一个特别的输入会导致最坏情况性态。这种算法的最坏情况性态是由随机数产生器决定的。你即使有意给出一个坏的输入也没用,因为随机化排列会使得输入数据的次序对算法不产生影响。只有在随机数产生器给出了一个很不巧的排列时,随机化算法的最坏情况性态才会出现。事实上可以证明几乎所有的排列都可使快速排序接近平均情况性态,只有非常少的几个排列才会导致算法的近最坏情况性态。

一般来说,当一个算法可按多条路子做下去,但又很难决定哪一条保证是好的选择时,随机化策略是很有用的。如果大部分选择都是好的,则随机地选一个就行了。通常,一个算法在其执行过程中要做很多选择。如果一个好的选择的获益大于坏的选择的代价,那么随机地做一个选择就能得到一个很有效的算法。我们在前文已经了解到,对快速排序来说,一组好坏相杂的划分仍能产生很好的运行时间 [2]  。因此我们可以认为该算法的随机化版本也能具有较好的性态。

参考资料

· 1.张德富.算法设计与分析:国防工业出版社,2009-8-1

· 2.算法导论(Introduction to Algorithms Second Edition)中文版 机械工业出版社 2010

最短路径

用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法能得出最短路径的最优解,但由于它遍历计算的节点很多,所以效率低。

中文名

最短路径

外文名

shortest path

    

一类经典算法问题

解决思路

由已知点/边向外扩展

解决方法

SPFA算法Dijkstra算法

目录

00001. 1 概述

00002. 2 解决方法

00003.  综述

00004. 3 Dijkstra算法

00005.  C++代码

概述

最短路径问题是图论研究中的一个经典算法问题, 旨在寻找图(由结点和路径组成的)中两结点之间的最短路径。 算法具体的形式包括:

确定起点的最短路径问题 - 即已知起始结点,求最短路径的问题。 [1] 

确定终点的最短路径问题 - 与确定起点的问题相反,该问题是已知终结结点,求最短路径的问题。在无向图中该问题与确定起点的问题完全等同,在有向图中该问题等同于把所有路径方向反转的确定起点的问题。

确定起点终点的最短路径问题 - 即已知起点和终点,求两结点之间的最短路径。

全局最短路径问题 - 求图中所有的最短路径。

解决方法

综述

用于解决最短路径问题的算法被称做最短路径算法, 有时被简称作路径算法。 最常用的路径算法有:

Dijkstra算法

SPFA算法\Bellman-Ford算法

Floyd算法\Floyd-Warshall算法

Johnson算法

A*算法

所谓单源最短路径问题是指:已知图G=VE),我们希望找出从某给定的源结点S∈VV中的每个结点的最短路径。 [1] 

首先,我们可以发现有这样一个事实:如果PG中从vsvj的最短路,viP中的一个点,那么,从vs沿Pvi的路是从vsvi的最短路。

Dijkstra算法

Dijkstra算法迪杰斯特拉)是典型的最短路径路由算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法能得出最短路径的最优解,但由于它遍历计算的节点很多,所以效率低。可以用堆优化。

Dijkstra算法是很有代表性的最短路算法,在很多专业课程中都作为基本内容有详细的介绍,如数据结构,图论,运筹学等等。

Dijkstra一般的表述通常有两种方式,一种用永久和临时标号方式,一种是用OPEN, CLOSE表方式,Drew为了和下面要介绍的 A* 算法和 D* 算法表述一致,这里均采用OPEN,CLOSE表的方式。

其采用的是贪心法的算法策略

大概过程:

创建两个表,OPEN, CLOSE

OPEN表保存所有已生成而未考察的节点,CLOSED表中记录已访问过的节点。

1. 访问路网中距离起始点最近且没有被检查过的点,把这个点放入OPEN组中等待检查。

2. 从OPEN表中找出距起始点最近的点,找出这个点的所有子节点,把这个点放到CLOSE表中。

3. 遍历考察这个点的子节点。求出这些子节点距起始点的距离值,放子节点到OPEN表中。

4. 重复第2和第3,直到OPEN表为空,或找到目标点。

C++代码

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

5

#include<iostream>

#include<vector>

using namespace std;

void dijkstra(const int &beg,//出发点

              const vector<vector<int> > &adjmap,//邻接矩阵,通过传引用避免拷贝

              vector<int> &dist,//出发点到各点的最短路径长度

              vector<int> &path)//路径上到达该点的前一个点

//负边被认作不联通

//福利:这个函数没有用任何全局量,可以直接复制!

{

    const int &NODE=adjmap.size();//用邻接矩阵的大小传递顶点个数,减少参数传递

    dist.assign(NODE,-1);//初始化距离为未知

    path.assign(NODE,-1);//初始化路径为未知

    vector<bool> flag(NODE,0);//标志数组,判断是否处理过

    dist[beg]=0;//出发点到自身路径长度为0

    while(1)

    {

        int v=-1;//初始化为未知

        for(int i=0; i!=NODE; ++i)

            if(!flag[i]&&dist[i]>=0)//寻找未被处理过且

                if(v<0||dist[i]<dist[v])//距离最小的点

                    v=i;

        if(v<0)return;//所有联通的点都被处理过

        flag[v]=1;//标记

        for(int i=0; i!=NODE; ++i)

            if(adjmap[v][i]>=0)//有联通路径且

                if(dist[i]<0||dist[v]+adjmap[v][i]<dist[i])//不满足三角不等式

                {

                    dist[i]=dist[v]+adjmap[v][i];//更新

                    path[i]=v;//记录路径

                }

    }

}

int main()

{

    int n_num,e_num,beg;//含义见下

    cout<<"输入点数、边数、出发点:";

    cin>>n_num>>e_num>>beg;

    vector<vector<int> > adjmap(n_num,vector<int>(n_num,-1));//默认初始化邻接矩阵

    for(int i=0,p,q; i!=e_num; ++i)

    {

        cout<<"输入第"<<i+1<<"条边的起点、终点、长度(负值代表不联通):";

        cin>>p>>q;

        cin>>adjmap[p][q];

    }

    vector<int> dist,path;//用于接收最短路径长度及路径各点

    dijkstra(beg,adjmap,dist,path);

    for(int i=0; i!=n_num; ++i)

    {

        cout<<beg<<"到"<<i<<"的最短距离为"<<dist[i]<<",反向打印路径:";

        for(int w=i; path[w]>=0; w=path[w])

            cout<<w<<"<-";

        cout<<beg<<'\n';

    }

}

参考资料

· 1.谢希仁、陈鸣、张兴元.计算机网络.北京:电子工业出版社,1994.10

词条标签:

科学百科信息科学分类 , 科学

贝叶斯分类

 编辑

本词条由科普中国百科科学词条编写与应用工作项目 审核 。

贝叶斯分类技术在众多分类技术中占有重要地位,也属于统计学分类的范畴,是一种非规则的分类方法,贝叶斯分类技术通过对已分类的样本子集进行训练,学习归纳出分类函数(对离散变量的预测称作分类,对连续变量的分类称为回归),利用训练得到的分类器实现对未分类数据的分类。通过对比分析不同的分类算法,发现朴素贝叶斯分类算法(Naive Bayes),一种简单的贝叶斯分类算法,其应用效果比神经网络分类算法和判定树分类算法还要好,特别是待分类数据量非常大时,贝叶斯分类方法相较其他分类算法具有高准确率 [1]  

中文名

贝叶斯分类

外文名

Bayesian classification

    

统计学,广泛应用于其他学科

    

分类方法

    

用概率来表示所有形式的不确定性

目录

00001. 1 背景介绍

00002. 2 贝叶斯分类的原理

00003. 3 贝叶斯分类特点

背景介绍

编辑

数据分类(Classification)在商业应用上具有重要意义,是数据挖掘中非常重要的一项研究内容。通常数据分类的做法是,基于样本数据先训练构建分类函数或者分类模型(也称为分类器),该分类器的具有将待分类数据项映射到某一特点类别的功能,数据分类和回归分析都可用于预测,预测是指从基于样本数据记录,根据分类准则自动给出对未知数据的推广描述,从而实现对未知数据进行预测 [1]  

贝叶斯分类是统计学的分类方法,其分析方法的特点是使用概率来表示所有形式的不确定性,学习或推理都要用概率规则来实现。

贝叶斯分类的原理

编辑

基于统计学的贝叶斯分类方法以贝叶斯理论为基础,通过求解后验概率分布,预测样本属于某一类别的概率。贝叶斯公式可写成如下形式:

P(y|x)=P(x|y)*P(A)*P(y)/(P(x) (4-1)其中,P(y I x)为后验概率分布,P(y)为先验分布,P(x)通常为常数。

 

为了简化运算,朴素贝叶斯分类算法假定任意属性对类别的影响与其他属性对类别的影响无关,这种假定称为类条件独立朴素假定。图4-3展示了朴树贝叶斯分类中属性和类之间的关系,如图4-3所示,C表示待分类别,A1, ..., A4表示样本属性,箭头表示属性变量和类别变量之间的依存关系,从图中可以看出,在朴素贝叶斯分类模型中,样本属性AiAj ( i不等于j)之间不存在相互依赖关系,他们仅与节点类C有关 [1]  

已知样本数据x =< x1 , . .. , xn >(样本数据x共有n种属性,其中xi表示第i个属性Ai的值)属于任意类,(y∈ { c1,,...,ck})(总共k个类别,cj表示第j个类)的概率。给定一个未分类的数据样本X,应用朴素贝叶斯分类算法,预测样本数据X属于具有最高后验概率的类,未知样本X属于类别c;的条件是,当且仅当

P(ciIX)>P (cjIX)1≤j≤k, (4-2)

 

贝叶斯分类(10张)

 

因此,将最大化后验概率P(ciIX)或者其对数形式称为最大后验假定,记为arg maxy P( y IX)

根据全概率公式,对于任意类别ci

在任意一次分类中取值均相等,也就是说,数据样本X产生的概率相同(P(X)定义为常数),因此,可以将后验概率P(yl X)表示成概率乘积正比关系式:

P(yIX)∝P(XIy)*P(y)

因此,求取arg maxyP( y IX)相当于求取arg maxyP(XIy);而arg maxyP(XIy)的计算要相对容易很多,所以,在实际应用中通常根据式(4-4 )来求解后验概率。

根据朴素贝叶斯分类算法的类条件独立假设,给定样本数据的类标号,各属性值xi之间相互条件独立,彼此不存在相互依赖关系。

也就是说,为对未知样本X分类,对每个类ci计算P(xl ci)P(ci);当且仅当P(Xlci)P(ci)>P(Xlcj)P(cj)1≤j≤mj≠i (4-7)

定义样本X属于类别ci,即X被指派到P(X I ci)P(ci)最大的类ci [1]  

贝叶斯分类特点

编辑

贝叶斯分类是统计学方法,它主要是基于贝叶斯定理。通过计算给定实例属于一个特定类的概率来对给定实例进行分类。贝叶斯分类具有以下特点:

(1)贝叶斯分类不把一个实例绝对的指派给某一种分类,而是通过计算得到实例属于某一分类的概率,具有最大概率的类就是该实例所属的分类;

(2)一般情况下在贝叶斯分类中所有属性都潜在的对分类结果发挥作用,能够使所有的属性都参与到分类中;

(3)贝叶斯分类实例的属性可以是离散的、连续的,也可以是混合的。

贝叶斯方法因其在理论上给出了最小化误差的最优解决方法而被广泛应用于分类问题。在贝叶斯方法的基础上,提出了贝叶斯网络((Bayesian Network, BN)方法。朴素贝叶斯分类就是假定一个属性对于给定分类的影响独立于其他属性。这一假定被称作条件独立,对实力属性的这种假设大大简化了分类所需的计算量。大量的研究结果表明,虽然BN算法对属性结点之间的连接结构进行了限制,但是朴素贝叶斯的分类器的分类性能优于标准的贝叶斯网络分类器 [2]  

词条图册更多图册

 

词条图片(2)

 

贝叶斯分类(10)

 

摄影测量与遥感学

 摄影测量学

 摄影学

 航天摄影

 航空摄影

 航空摄影机

 非量测摄影机

 立体摄影机

 量测摄影机

 全景摄影机

 框幅摄影机

 条幅[航带]摄影机

 CCD摄影机

 多谱段摄影机

 弹道摄影机

 地面摄影机

 水下摄影机

 大像幅摄影机

 恒星摄影机

 地平线摄影机

 反束光导管摄影机

 摄影机检校

 像幅

 框标

 像移补偿

 焦距

 快门

 中心式快门

 帘幕式快门

 景深

 超焦点距离

 光圈

 光圈号数

 像场角

 瞬时视场

 摄影测量畸变差

 全景畸变

 径向畸变

 切向畸变

 物镜分辨力

 正片

 负片

 透明负片

 透明正片

 反转片

 色盲片

其他科技名词

参考资料

· 1.基于叠前道集的储层参数反演方法研究   .中国知网.2011[引用日期2017-09-16]

· 2.基于油田压裂微地震监测的震相识别与震源定位方法研究   .中国知网.2012[引用日期2017-09-16]

神经网络

神经网络可以指向两种,一个是生物神经网络,一个是人工神经网络。

生物神经网络:一般指生物大脑神经元细胞触点等组成的网络,用于产生生物的意识,帮助生物进行思考行动

人工神经网络(Artificial Neural Networks,简写为ANNs)也简称为神经网络(NNs)或称作连接模型(Connection Model),它是一种模仿动物神经网络行为特征,进行分布式并行信息处理的算法数学模型。这种网络依靠系统的复杂程度,通过调整内部大量节点之间相互连接的关系,从而达到处理信息的目的。

人工神经网络:是一种应用类似于大脑神经突触联接的结构进行信息处理的数学模型。在工程与学术界也常直接简称为神经网络或类神经网络。

    

神经网络

    

侯媛彬,杜京义,汪梅

ISBN

 9787560619026

    

 223

    

26.00

出版社

西安电子科技大学出版社

出版时间

2007-8-1

    

人工神经网络,生物神经网络

目录

00001. 1 研究内容

00002.  生物原型

00003.  建立模型

00004.  算法

00005.  应用

00006. 2 机器学习和相关领域:

00001. 3 分类:

00002. 4 工作原理

00003. 5 发展历史

00004. 6 常见的工具

00005. 7 研究方向

00001. 8 相关书籍

00002. 9 内容简介

00003. 10 作者简介

00004. 11 目录总览

研究内容

神经网络的研究内容相当广泛,反映了多学科交叉技术领域的特点。主要的研究工作集中在以下几个方面:

生物原型

从生理学、心理学、解剖学、脑科学、病理学等方面研究神经细胞、神经网络、神经系统的生物原型结构及其功能机理。

建立模型

根据生物原型的研究,建立神经元、神经网络的理论模型。其中包括概念模型、知识模型、物理化学模型、数学模型等。

算法

在理论模型研究的基础上构作具体的神经网络模型,以实现计算机模拟

人类的神经网络

准备制作硬件,包括网络学习算法的研究。这方面的工作也称为技术模型研究。

神经网络用到的算法就是向量乘法,并且广泛采用符号函数及其各种逼近。并行、容错、可以硬件实现以及自我学习特性,是神经网络的几个基本优点,也是神经网络计算方法与传统方法的区别所在。

应用

在网络模型与算法研究的基础上,利用人工神经网络组成实际的应用系统,例如,完成某种信号处理或模式识别的功能、构作专家系统、制成机器人、复杂系统控制等等。

纵观当代新兴科学技术的发展历史,人类在征服宇宙空间、基本粒子,生命起源等科学技术领域的进程中历经了崎岖不平的道路。我们也会看到,探索人脑功能和神经网络的研究将伴随着重重困难的克服而日新月异。

机器学习和相关领域:

在机器学习和相关领域,人工神经网络(人工神经网络)的计算模型灵感来自动物的中枢神经系统(尤其是脑),并且被用于估计或可以依赖于大量的输入和一般的未知近似函数。人工神经网络通常呈现为相互连接的神经元,它可以从输入的计算值,并且能够机器学习以及模式识别由于它们的自适应性质的系统。

例如,用于手写体识别的神经网络是由一组可能被输入图像的像素激活的输入神经元来限定。后进过加权,并通过一个函数(由网络的设计者确定的)转化,这些神经元的致动被上到其他神经元然后被传递。重复此过程,直到最后,一输出神经元被激活。这决定了哪些字符被读取。

像其他的从数据-神经网络认识到的机器学习系统方法已被用来解决各种各样的很难用普通的以规则为基础的编程解决的任务,包括计算机视觉和语音识别。

也许,人工神经网络的最大优势是他们能够被用作一个任意函数逼近的机制,那是从观测到的数据学习。然而,使用起来也不是那么简单的,一个比较好理解的基本理论是必不可少的。

分类:

选择模式:这将取决于数据的表示和应用。过于复杂的模型往往会导致问题的学习。

学习算法:在学习算法之间有无数的权衡。几乎所有的算法为了一个特定的数据集训练将会很好地与正确的超参数合作。然而,选择和调整的算法上看不见的数据训练需要显著量的实验。

稳健性:如果该模型中,成本函数和学习算法,适当地选择所得到的神经网络可以是非常健壮的。有了正确的实施,人工神经网络,可以自然地应用于在线学习和大型数据集的应用程序。其简单的实现和表现在结构上主要依赖本地的存在,使得在硬件快速,并行实现。

工作原理

人脑是如何工作的?

人类能否制作模拟人脑的人工神经元

多少年以来,人们从医学生物学生理学哲学信息学计算机科学、认知学、组织协同学等各个角度企图认识并解答上述问题。在寻找上述问题答案的研究过程中,逐渐形成了一个新兴的多学科交叉技术领域,称之为神经网络。神经网络的研究涉及众多学科领域,这些领域互相结合、相互渗透并相互推动。不同领域的科学家又从各自学科的兴趣与特色出发,提出不同的问题,从不同的角度进行研究。

人工神经网络首先要以一定的学习准则进行学习,然后才能工作。现以人工神经网络对于写“A”“B”两个字母的识别为例进行说明,规定当“A”输入网络时,应该输出“1”,而当输入为“B”时,输出为“0”

所以网络学习的准则应该是:如果网络作出错误的判决,则通过网络的学习,应使得网络减少下次犯同样错误的可能性。首先,给网络的各连接权值赋予(01)区间内的随机值,将“A”所对应的图象模式输入给网络,网络将输入模式加权求和、与门限比较、再进行非线性运算,得到网络的输出。在此情况下,网络输出为“1”“0”概率各为50%,也就是说是完全随机的。这时如果输出为“1”(结果正确),则使连接权值增大,以便使网络再次遇到“A”模式输入时,仍然能作出正确的判断。

普通计算机的功能取决于程序中给出的知识和能力。显然,对于智能活动要通过总结编制程序将十分困难。

人工神经网络也具有初步的自适应与自组织能力。在学习或训练过程中改变突触权重值,以适应周围环境的要求。同一网络因学习方式及内容不同可具有不同的功能。人工神经网络是一个具有学习能力的系统,可以发展知识,以致超过设计者原有的知识水平。通常,它的学习训练方式可分为两种,一种是有监督或称有导师的学习,这时利用给定的样本标准进行分类或模仿;另一种是无监督学习或称无为导师学习,这时,只规定学习方式或某些规则,则具体的学习内容随系统所处环境 (即输入信号情况)而异,系统可以自动发现环境特征和规律性,具有更近似人脑的功能。

神经网络就像是一个爱学习的孩子,您教她的知识她是不会忘记而且会学以致用的。我们把学习集(Learning Set)中的每个输入加到神经网络中,并告诉神经网络输出应该是什么分类。在全部学习集都运行完成之后,神经网络就根据这些例子总结出她自己的想法,到底她是怎么归纳的就是一个黑盒了。之后我们就可以把测试集(Testing Set)中的测试例子用神经网络来分别作测试,如果测试通过(比如80%90%的正确率),那么神经网络就构建成功了。我们之后就可以用这个神经网络来判断事务的分类了。

神经网络是通过对人脑的基本单元——神经元的建模和联接,探索模拟人脑神经系统功能的模型,并研制一种具有学习、联想、记忆和模式识别等智能信息处理功能的人工系统。神经网络的一个重要特性是它能够从环境中学习,并把学习的结果分布存储于网络的突触连接中。神经网络的学习是一个过程,在其所处环境的激励下,相继给网络输入一些样本模式,并按照一定的规则(学习算法)调整网络各层的权值矩阵,待网络各层权值都收敛到一定值,学习过程结束。然后我们就可以用生成的神经网络来对真实数据做分类。

人工神经网络早期的研究工作应追溯至20世纪40年代。下面以时间顺序,以著名的人物或某一方面突出的研究成果为线索,简要介绍

发展历史

1943年,心理学家W·Mcculloch数理逻辑学家W·Pitts在分析、总结神经元基本特性的基础上首先提出神经元的数学模型。此模型沿用至今,并且直接影响着这一领域研究的进展。因而,他们两人可称为人工神经网络研究的先驱。

1945·诺依曼领导的设计小组试制成功存储程序式电子计算机,标志着电子计算机时代的开始。1948年,他在研究工作中比较了人脑结构与存储程序式计算机的根本区别,提出了以简单神经元构成的再生自动机网络结构。但是,由于指令存储式计算机技术的发展非常迅速,迫使他放弃了神经网络研究的新途径,继续投身于指令存储式计算机技术的研究,并在此领域作出了巨大贡献。虽然,冯·诺依曼的名字是与普通计算机联系在一起的,但他也是人工神经网络研究的先驱之一。

50年代末,F·Rosenblatt设计制作了感知机,它是一种多层的神经网络。这项工作首次把人工神经网络的研究从理论探讨付诸工程实践。当时,世界上许多实验室仿效制作感知机,分别应用于文字识别、声音识别、声纳信号识别以及学习记忆问题的研究。然而,这次人工神经网络的研究高潮未能持续很久,许多人陆续放弃了这方面的研究工作,这是因为当时数字计算机的发展处于全盛时期,许多人误以为数字计算机可以解决人工智能、模式识别、专家系统等方面的一切问题,使感知机的工作得不到重视;其次,当时的电子技术工艺水平比较落后,主要的元件是电子管晶体管,利用它们制作的神经网络体积庞大,价格昂贵,要制作在规模上与真实的神经网络相似是完全不可能的;另外,在1968年一本名为《感知机》的著作中指出线性感知机功能是有限的,它不能解决如异或这样的基本问题,而且多层网络还不能找到有效的计算方法,这些论点促使大批研究人员对于人工神经网络的前景失去信心。60年代末期,人工神经网络的研究进入了低潮。

另外,在60年代初期,Widrow提出了自适应线性元件网络,这是一种连续取值的线性加权求和阈值网络。后来,在此基础上发展了非线性多层自适应网络。当时,这些工作虽未标出神经网络的名称,而实际上就是一种人工神经网络模型。

随着人们对感知机兴趣的衰退,神经网络的研究沉寂了相当长的时间。80年代初期,模拟与数字混合的超大规模集成电路制作技术提高到新的水平,完全付诸实用化,此外,数字计算机的发展在若干应用领域遇到困难。这一背景预示,向人工神经网络寻求出路的时机已经成熟。美国的物理学家Hopfield1982年和1984年在美国科学院院刊上发表了两篇关于人工神经网络研究的论文,引起了巨大的反响。人们重新认识到神经网络的威力以及付诸应用的现实性。随即,一大批学者和研究人员围绕着 Hopfield提出的方法展开了进一步的工作,形成了80年代中期以来人工神经网络的研究热潮。

常见的工具

在众多的神经网络工具中,NeuroSolutions [1]  始终处于业界领先位置。它是一个可用于windows XP/7高度图形化的神经网络开发工具。其将模块化,基于图标的网络设计界面,先进的学习程序和遗传优化进行了结合。该款可用于研究和解决现实世界的复杂问题的神经网络设计工具在使用上几乎无限制。

研究方向

神经网络的研究可以分为理论研究和应用研究两大方面。

理论研究可分为以下两类:

1、利用神经生理与认知科学研究人类思维以及智能机理。

2、利用神经基础理论的研究成果,用数理方法探索功能更加完善、性能更加优越的神经网络模型,深入研究网络算法和性能,如:稳定性、收敛性、容错性、鲁棒性等;开发新的网络数理理论,如:神经网络动力学、非线性神经场等。

应用研究可分为以下两类:

1、神经网络的软件模拟和硬件实现的研究。

2、神经网络在各个领域中应用的研究。这些领域主要包括:

模式识别、信号处理、知识工程、专家系统、优化组合、机器人控制等。随着神经网络理论本身以及相关理论、相关技术的不断发展,神经网络的应用定将更加深入。

相关书籍

 名:《神经网络》

作 者: 侯媛彬,杜京义,汪梅 编著

  社:西安电子科技大学出版社

出版时间: 2007-8-1

内容简介

神经网络是智能控制技术的主要分支之一。本书的主要内容有:神经网络的概念,神经网络的分类与学习方法,前向神经网络模型及其算法,改进的BP网络及其控制、辨识建模,基于遗传算法的神经网络,基于模糊理论的神经网络,RBF网络及其在混沌背景下对微弱信号的测量与控制,反馈网络,Hopfield网络及其在字符识别中的应用,支持向量机及其故障诊断,小波神经网络及其在控制与辨识中的应用。

本书内容全面,重点突出,以讲明基本概念和方法为主,尽量减少繁琐的数学推导,并给出一些结合工程应用的例题。本书附有光盘,其中包括结合各章节内容所开发的30多个源程序,可直接在MATLAB界面下运行,此外,还包括用AuthorwareFlash软件制作的动画课件。

本书既可作为自动化和电气自动化专业及相关专业的研究生教材,也可供机电类工程技术人员选用,还可作为有兴趣的读者自学与应用的参考书。

作者简介

侯媛彬 [2]  ,教授,女,博士生导师1997年获西安交通大学系统工程()博士学位。西安科技大学矿山机电博士点学科带头人,西安科技大学省重点学科控制理论与控制工程学科带头人,中国自动化学会电气专业委员会委员,陕西省自动化协会常务理事兼教育委员会主任。一直从事自动化、安全技术与工程方面的教学和研究工作。讲授过博士、硕士和本科各层面的专业课程10多门。在国内外公开发表学术论文110余篇,其中被EIISTP检索30余篇。出版专著、教材8部:承担省部级科研项目及横向项目10余项;获实用新型专利2项;获省级科技进步奖3项:获科研、教学方面的各种奖10多项;2006年获省级师德标兵。

目录总览

前言

1智能控制技术基础

2章 神经网络控制的基本概念

3章 前向神经网络模型及其仿真算法

4章 改进的BP网络训练算法

5章 小脑模型神经网络及其应用

6章 遗传算法及其神经网络

7模糊神经网络

8章 径向基函数网络

9章 反馈型神经网络

10章 支持向量机

11章 小波神经网络及应用

参考文献

参考资料

· 1.NeuroSolutions介绍 .慧都软件[引用日期2013-04-23]

· 2.图书。神经网络

词条标签:

科学百科信息科学分类 , 中国电子学会 , 非生物 , 科学 , 文化术语 , 文化 , 书籍

聚类

聚类

 锁定

本词条由科普中国百科科学词条编写与应用工作项目 审核 。

将物理或抽象对象的集合分成由类似的对象组成的多个类的过程被称为聚类。由聚类所生成的簇是一组数据对象的集合,这些对象与同一个簇中的对象彼此相似,与其他簇中的对象相异。物以类聚,人以群分,在自然科学和社会科学中,存在着大量的分类问题。聚类分析又称群分析,它是研究(样品或指标)分类问题的一种统计分析方法。聚类分析起源于分类学,但是聚类不等于分类。聚类与分类的不同在于,聚类所要求划分的类是未知的。聚类分析内容非常丰富,有系统聚类法、有序样品聚类法、动态聚类法、模糊聚类法图论聚类法、聚类预报法等。

在数据挖掘中,聚类也是很重要的一个概念。

中文名

聚类

    

将物理或抽象对象的集合分成

对象相异

物以类聚,人以群分

群分析

聚类分析

目录

00001. 1 典型应用

00002. 2 典型要求

00003. 3 计算方法

00004. 4 研究情况

典型应用

聚类的典型应用是什么?在商务上,聚类能帮助市场分析人员从客户基本库中发现不同的客户群,并且用购买模式来刻画不同的客户群的特征。在生物学上,聚类能用于推导植物和动物的分类,对基因进行分类,获得对种群中固有结构的认识。聚类在地球观测数据库中相似地区的确定,汽车保险单持有者的分组,及根据房子的类型、价值和地理位置对一个城市中房屋的分组上也可以发挥作用。聚类也能用于对Web上的文档进行分类,以发现信息。

典型要求

可伸缩性:许多聚类算法在小于 200 个数据对象的小数据集合上工作得很好;但是,一个大规模数据库可能包含几百万个对象,在这样的大数据集合样本上进行聚类可能会导致有偏的结果。我们需要具有高度可伸缩性的聚类算法 [1] 

处理不同类型数据的能力:许多算法被设计用来聚类数值类型的数据。但是,应用可能要求聚类其他类型的数据,如二元类型(binary),分类/标称类型(categorical/nominal),序数型(ordinal)数据,或者这些数据类型的混合。

发现任意形状的聚类:许多聚类算法基于欧几里得或者曼哈顿距离度量来决定聚类。基于这样的距离度量的算法趋向于发现具有相近尺度和密度的球状簇。但是,一个簇可能是任意形状的。提出能发现任意形状簇的算法是很重要的。

用于决定输入参数的领域知识最小化:许多聚类算法聚类分析中要求用户输入一定的参数,例如希望产生的簇的数目。聚类结果对于输入参数十分敏感。参数通常很难确定,特别是对于包含高维对象的数据集来说。这样不仅加重了用户的负担,也使得聚类的质量难以控制。

处理噪声数据的能力:绝大多数现实中的数据库都包含了孤立点,缺失,或者错误的数据。一些聚类算法对于这样的数据敏感,可能导致低质量的聚类结果。

对于输入记录的顺序不敏感:一些聚类算法对于输入数据的顺序是敏感的。例如,同一个数据集合,当以不同的顺序交给同一个算法时,可能生成差别很大的聚类结果。开发对数据输入顺序不敏感的算法具有重要的意义。

高维度(high dimensionality):一个数据库或者数据仓库可能包含若干维或者属性。许多聚类算法擅长处理低维的数据,可能只涉及两到三维。人类的眼睛在最多三维的情况下能够很好地判断聚类的质量。在高维空间中聚类数据对象是非常有挑战性的,特别是考虑到这样的数据可能分布非常稀疏,而且高度偏斜。

基于约束的聚类:现实世界的应用可能需要在各种约束条件下进行聚类。假设你的工作是在一个城市中为给定数目的自动提款机选择安放位置,为了作出决定,你可以对住宅区进行聚类,同时考虑如城市的河流和公路网,每个地区的客户要求等情况。要找到既满足特定的约束,又具有良好聚类特性的数据分组是一项具有挑战性的任务。

可解释性和可用性:用户希望聚类结果是可解释的,可理解的,和可用的。也就是说,聚类可能需要和特定的语义解释和应用相联系。应用目标如何影响聚类方法的选择也是一个重要的研究课题。

计算方法

传统的聚类分析计算方法主要有如下几种:

1、划分方法(partitioning methods)

给定一个有N个元组或者纪录的数据集,分裂法将构造K个分组,每一个分组就代表一个聚类,K<N。而且这K个分组满足下列条件:(1) 每一个分组至少包含一个数据纪录;(2)每一个数据纪录属于且仅属于一个分组(注意:这个要求在某些模糊聚类算法中可以放宽);对于给定的K,算法首先给出一个初始的分组方法,以后通过反复迭代的方法改变分组,使得每一次改进之后的分组方案都较前一次好,而所谓好的标准就是:同一分组中的记录越近越好,而不同分组中的纪录越远越好。使用这个基本思想的算法有:K-MEANS算法K-MEDOIDS算法、CLARANS算法;

大部分划分方法是基于距离的。给定要构建的分区数k,划分方法首先创建一个初始化划分。然后,它采用一种迭代的重定位技术,通过把对象从一个组移动到另一个组来进行划分。一个好的划分的一般准备是:同一个簇中的对象尽可能相互接近或相关,而不同的簇中的对象尽可能远离或不同。还有许多评判划分质量的其他准则。传统的划分方法可以扩展到子空间聚类,而不是搜索整个数据空间。当存在很多属性并且数据稀疏时,这是有用的。为了达到全局最优,基于划分的聚类可能需要穷举所有可能的划分,计算量极大。实际上,大多数应用都采用了流行的启发式方法,如k-均值和k-中心算法,渐近的提高聚类质量,逼近局部最优解。这些启发式聚类方法很适合发现中小规模的数据库中小规模的数据库中的球状簇。为了发现具有复杂形状的簇和对超大型数据集进行聚类,需要进一步扩展基于划分的方法。 [2] 

2、层次方法(hierarchical methods)

这种方法对给定的数据集进行层次似的分解,直到某种条件满足为止。具体又可分为自底向上自顶向下两种方案。例如在自底向上方案中,初始时每一个数据纪录都组成一个单独的组,在接下来的迭代中,它把那些相互邻近的组合并成一个组,直到所有的记录组成一个分组或者某个条件满足为止。代表算法有:BIRCH算法、CURE算法、CHAMELEON算法等;

层次聚类方法可以是基于距离的或基于密度或连通性的。层次聚类方法的一些扩展也考虑了子空间聚类。层次方法的缺陷在于,一旦一个步骤(合并或分裂)完成,它就不能被撤销。这个严格规定是有用的,因为不用担心不同选择的组合数目,它将产生较小的计算开销。然而这种技术不能更正错误的决定。已经提出了一些提高层次聚类质量的方法。 [2] 

3、基于密度的方法(density-based methods)

基于密度的方法与其它方法的一个根本区别是:它不是基于各种各样的距离的,而是基于密度的。这样就能克服基于距离的算法只能发现类圆形的聚类的缺点。这个方法的指导思想就是,只要一个区域中的点的密度大过某个阀值,就把它加到与之相近的聚类中去。代表算法有:DBSCAN算法、OPTICS算法、DENCLUE算法等;

4、基于网格的方法(grid-based methods)

这种方法首先将数据空间划分成为有限个单元(cell)的网格结构,所有的处理都是以单个的单元为对象的。这么处理的一个突出的优点就是处理速度很快,通常这是与目标数据库中记录的个数无关的,它只与把数据空间分为多少个单元有关。代表算法有:STING算法、CLIQUE算法、WAVE-CLUSTER算法;

很多空间数据挖掘问题,使用网格通常都是一种有效的方法。因此,基于网格的方法可以和其他聚类方法集成。 [2] 

5、基于模型的方法(model-based methods)

基于模型的方法给每一个聚类假定一个模型,然后去寻找能够很好的满足这个模型的数据集。这样一个模型可能是数据点在空间中的密度分布函数或者其它。它的一个潜在的假定就是:目标数据集是由一系列的概率分布所决定的。通常有两种尝试方向:统计的方案和神经网络的方案。

当然聚类方法还有:传递闭包法,布尔矩阵法,直接聚类法,相关性分析聚类,基于统计的聚类方法等。

研究情况

传统的聚类已经比较成功的解决了低维数据的聚类问题。但是由于实际应用中数据的复杂性,在处理许多问题时,现有的算法经常失效,特别是对于高维数据和大型数据的情况。因为传统聚类方法在高维数据集中进行聚类时,主要遇到两个问题。高维数据集中存在大量无关的属性使得在所有维中存在簇的可能性几乎为零;高维空间中数据较低维空间中数据分布要稀疏,其中数据间距离几乎相等是普遍现象,而传统聚类方法是基于距离进行聚类的,因此在高维空间中无法基于距离来构建簇。

高维聚类分析已成为聚类分析的一个重要研究方向。同时高维数据聚类也是聚类技术的难点。随着技术的进步使得数据收集变得越来越容易,导致数据库规模越来越大、复杂性越来越高,如各种类型的贸易交易数据、Web 文档、基因表达数据等,它们的维度(属性)通常可以达到成百上千维,甚至更高。但是,受维度效应的影响,许多在低维数据空间表现良好的聚类方法运用在高维空间上往往无法获得好的聚类效果。高维数据聚类分析是聚类分析中一个非常活跃的领域,同时它也是一个具有挑战性的工作。高维数据聚类分析在市场分析、信息安全、金融、娱乐、反恐等方面都有很广泛的应用。

参考资料

· 1.数据聚类 .维基百科[引用日期2013-04-20]

· 2.Jiawei Han.数据挖掘概念与技术:机械工业出版社,2012

时间序列

时间序列

 编辑

本词条由科普中国百科科学词条编写与应用工作项目 审核 。

时间序列(或称动态数列)是指将同一统计指标的数值按其发生的时间先后顺序排列而成的数列。时间序列分析的主要目的是根据已有的历史数据对未来进行预测。

中文名

时间序列

外文名

time series

    

序列

构成要素

现象所属的时间等

要素一

时间

要素二

指标数值

    

描述现象的发展状态和结果

目录

00001. 1 时间序列

00002. 2 作用

00003. 3 种类

00001. 4 特征

00002. 5 编制原则

00003. 6 变量特征

00001. 7 分析方法

00002. 8 分析模型

00003. 9 预测

时间序列

编辑

构成要素:长期趋势,季节变动,循环变动,不规则变动

长期趋势( T )现象在较长时期内受某种根本性因素作用而形成的总的变动趋势

季节变动( S )现象在一年内随着季节的变化而发生的有规律的周期性变动

循环变动( C )现象以若干年为周期所呈现出的波浪起伏形态的有规律的变动

不规则变动()是一种无规律可循的变动,包括严格的随机变动和不规则的突发性影响很大的变动两种类型

作用

编辑

1. 可以反映社会经济现象的发展变化过程,描述现象的发展状态和结果。

2. 可以研究社会经济现象的发展趋势和发展速度。

3. 可以探索现象发展变化的规律,对某些社会经济现象进行预测。

4. 利用时间序列可以在不同地区或国家之间进行对比分析,这也是统计分析的重要方法之一。

种类

编辑

()绝对数时间序列

1. 时期序列:由时期总量指标排列而成的时间序列 。

时期序列的主要特点有:

1)序列中的指标数值具有可加性。

2)序列中每个指标数值的大小与其所反映的时期长短有直接联系。

3)序列中每个指标数值通常是通过连续不断登记汇总取得的。

2. 时点序列:由时点总量指标排列而成的时间序列

时点序列的主要特点有:

1)序列中的指标数值不具可加性。

2)序列中每个指标数值的大小与其间隔时间的长短没有直接联系。

3)序列中每个指标数值通常是通过定期的一次登记取得的。

()相对数时间序列

把一系列同种相对数指标按时间先后顺序排列而成的时间序列叫做相对数时间序列。

()平均数时间序列

平均数时间序列是指由一系列同类平均指标按时间先后顺序排列的时间序列。

特征

编辑

1、时间序列分析法是根据过去的变化趋势预测未来的发展,它的前提是假定事物的过去延续到未来。

时间序列分析,正是根据客观事物发展的连续规律性,运用过去的历史数据,通过统计分析,进一步推测未来的发展趋势。事物的过去会延续到未来这个假设前提包含两层含义:一是不会发生突然的跳跃变化,是以相对小的步伐前进;二是过去和当前的现象可能表明现在和将来活动的发展变化趋向。这就决定了在一般情况下,时间序列分析法对于短、近期预测比较显著,但如延伸到更远的将来,就会出现很大的局限性,导致预测值偏离实际较大而使决策失误。

2、时间序列数据变动存在着规律性与不规律性

时间序列中的每个观察值大小,是影响变化的各种不同因素在同一时刻发生作用的综合结果。从这些影响因素发生作用的大小和方向变化的时间特性来看,这些因素造成的时间序列数据的变动分为四种类型。

(1)趋势性:某个变量随着时间进展或自变量变化,呈现一种比较缓慢而长期的持续上升、下降、停留的同性质变动趋向,但变动幅度可能不相等。

(2)周期性:某因素由于外部影响随着自然季节的交替出现高峰与低谷的规律。

(3)随机性:个别为随机变动,整体呈统计规律。

(4)综合性:实际变化情况是几种变动的叠加或组合。预测时设法过滤除去不规则变动,突出反映趋势性和周期性变动。

编制原则

编辑

保证序列中各期指标数值的可比性

()时期长短最好一致

()总体范围应该一致

()指标的经济内容应该统一

()计算方法应该统一

()计算价格和计量单位可比

变量特征

编辑

非平稳性(nonstationarity,也译作不平稳性,非稳定性):即时间序列变量无法呈现出一个长期趋势并最终趋于一个常数或是一个线性函数。

波动幅度随时间变化(Timevarying Volatility):即一个时间序列变量的方差随时间的变化而变化这两个特征使得有效分析时间序列变量十分困难。

平稳型时间数列(Stationary Time Series)系指一个时间数列其统计特性将不随时间之变化而改变者。

分析方法

编辑

(一)指标分析法

通过时间序列的分析指标来揭示现象的发展变化状况和发展变化程度。

(二)构成因素分析法

通过对影响时间序列的构成因素进行分解分析,揭示现象随时间变化而演变的规律。

分析模型

编辑

时间数列的组合模型

加法模型:Y=T+S+C+I (Y,T 计量单位相同的总量指标)(S,C,I 对长期趋势产生的或正或负的偏差)

乘法模型:Y=T·S·C·I(常用模型) (Y,T 计量单位相同的总量指标)(S,C,I 对原数列指标增加或减少的百分比)

预测

编辑

时间序列预测主要是以连续性原理作为依据的。连续性原理是指客观事物的发展具有合乎规律的连续性,事物发展是按照它本身固有的规律进行的。在一定条件下,只要规律赖以发生作用的条件不产生质的变化,则事物的基本发展趋势在未来就还会延续下去。

时间序列预测就是利用统计技术与方法,从预测指标的时间序列中找出演变模式,建立数学模型,对预测指标的未来发展趋势做出定量估计 [1]  

参考资料

· 1.辛治运,顾明.基于最小二乘支持向量机的复杂金融时间序列预测[J].清华大学学报(自然科学版),2008,48(7):1147-1149.  .万方数据库[引用日期2017-09-09]

词条标签:

科学百科数理科学分类 , 科技术语 , 科学

推荐系统

推荐系统

 编辑

本词条由科普中国百科科学词条编写与应用工作项目 审核 。

推荐系统是利用 [1]  电子商务网站向客户提供商品信息和建议,帮助用户决定应该购买什么产品,模拟销售人员帮助客户完成购买过程。个性化推荐是根据用户的兴趣特点和购买行为,向用户推荐用户感兴趣的信息和商品。

随着电子商务规模的不断扩大,商品个数和种类快速增长,顾客需要花费大量的时间才能找到自己想买的商品。这种浏览大量无关的信息和产品过程无疑会使淹没在信息过载问题中的消费者不断流失。

为了解决这些问题,个性化推荐系统应运而生。个性化推荐系统是建立在海量数据挖掘基础上的一种高级商务智能平台,以帮助电子商务网站为其顾客购物提供完全个性化的决策支持和信息服务。

中文名

推荐系统

外文名

Recommender system

    

电子商务

    

电商网站向客户提供商品信息建议

目录

00001. 1 背景简介

00002.  定义

00003. 2 发展历程

00004. 3 主要推荐方法

00005.  基于内容推荐

00001.  协同过滤推荐

00002.  基于关联规则推荐

00003.  基于效用推荐

00004.  基于知识推荐

00005.  组合推荐

00006. 4 体系结构

00001.  服务器端推荐系统

00002.  客户端推荐系统

00003. 5 知名团队

背景简介

编辑

互联网的出现和普及给用户带来了大量的信息,满足了用户在信息时代对信息的需求,但随着网络的迅速发展而带来的网上信息量的大幅增长,使得用户在面对大量信息时无法从中获得对自己真正有用的那部分信息,对信息的使用效率反而降低了,这就是所谓的 [2]  信息超载informationoverload)问题。

解决信息超载问题一个非常有潜力的办法是 [3]  推荐系统,它是根据用户的信息需求、兴趣等,将用户感兴趣的信息、产品等推荐给用户的个性化信息推荐系统。和搜索引擎相比推荐系统通过研究用户的兴趣偏好,进行个性化计算,由系统发现用户的兴趣点,从而引导用户发现自己的信息需求。一个好的推荐系统不仅能为用户提供个性化的服务,还能和用户之间建立密切关系,让用户对推荐产生依赖。

推荐系统现已广泛应用于很多领域,其中最典型并具有良好的发展和应用前景的领域就是电子商务领域。同时学术界对推荐系统的研究热度一直很高,逐步形成了一门独立的学科。

定义

推荐系统模型

推荐系统有3个重要的模块:用户建模模块、推荐对象建模模块、推荐算法模块。通用的推荐系统模型流程如图。推荐系统把用户模型中兴趣需求信息和推荐对象模型中的特征信息匹配,同时使用相应的推荐算法进行计算筛选,找到用户可能感兴趣的推荐对象,然后推荐给用户。

发展历程

编辑

19953月,卡耐基.梅隆大学的RobertArmstrong等人在美国人工智能协会上提出了个性化导航系统Web Watcher斯坦福大学MarkoBalabanovic等人在同一会议上推出了个性化推荐系统LIRA

19958月,麻省理工学院Henry Lieberman在国际人工智能联合大会(IJCAI)上提出了个性化导航智能体Letizia

1996年, Yahoo 推出了个性化入口My Yahoo

1997年,AT&T实验室提出了基于协作过滤的个性化推荐系统PHOAKSReferral Web;

1999年,德国Dresden技术大学的Tanja Joerding实现了个性化电子商务原型系统TELLIM

2000年,NEC研究院的Kurt等人为搜索引擎CiteSeer增加了个性化推荐功能;

2001年,纽约大学Gediminas AdoaviciusAlexander Tuzhilin实现了个性化电子商务网站的用户建模系统11Pro

2001年,IBM公司在其电子商务平台Websphere中增加了个性化功能,以便商家开发个性化电子商务网站。

2003年,Google开创了AdWords盈利模式,通过用户搜索的关键词来提供相关的广告。AdWords的点击率很高,是Google广告收入的主要来源。20073月开始,GoogleAdWords添加了个性化元素。不仅仅关注单次搜索的关键词,而是对用户一段时间内的搜索历史进行记录和分析,据此了解用户的喜好和需求,更为精确地呈现相关的广告内容。

2007年,雅虎推出了SmartAds广告方案。雅虎掌握了海量的用户信息,如用户的性别、年龄、收入水平、地理位置以及生活方式等,再加上对用户搜索、浏览行为的记录,使得雅虎可以为用户呈现个性化的横幅广告。

2009年,Overstock(美国著名的网上零售商)开始运用ChoiceStream公司制作的个性化横幅广告方案,在一些高流量的网站上投放产品广告。 Overstock在运行这项个性化横幅广告的初期就取得了惊人的成果,公司称:广告的点击率是以前的两倍,伴随而来的销售增长也高达20%30%

20097月,国内首个推荐系统科研团队北京百分点信息科技有限公司成立,该团队专注于推荐引擎技术与解决方案,在其推荐引擎技术与数据平台上汇集了国内外百余家知名电子商务网站与资讯类网站,并通过这些B2C网站每天为数以千万计的消费者提供实时智能的商品推荐。

20119月,百度世界大会2011上,李彦宏将推荐引擎与云计算、搜索引擎并列为未来互联网重要战略规划以及发展方向。百度新首页将逐步实现个性化,智能地推荐出用户喜欢的网站和经常使用的APP

主要推荐方法

编辑

基于内容推荐

基于内容的推荐(Content-based Recommendation)是 [4]  信息过滤技术的延续与发展,它是建立在项目的内容信息上作出推荐的,而不需要依据用户对项目的评价意见,更多地需要用机 器学习的方法从关于内容的特征描述的事例中得到用户的兴趣资料。在基于内容的推荐系统中,项目或对象是通过相关的特征的属性来定义,系统基于用户评价对象 的特征,学习用户的兴趣,考察用户资料与待预测项目的相匹配程度。用户的资料模型取决于所用学习方法,常用的有决策树、神经网络和基于向量的表示方法等。 基于内容的用户资料是需要有用户的历史数据,用户资料模型可能随着用户的偏好改变而发生变化。

基于内容推荐方法的优点是:
1)不需要其它用户的数据,没有冷开始问题和稀疏问题。
2)能为具有特殊兴趣爱好的用户进行推荐。
3)能推荐新的或不是很流行的项目,没有新项目问题。
4)通过列出推荐项目的内容特征,可以解释为什么推荐那些项目。
5)已有比较好的技术,如关于分类学习方面的技术已相当成熟。

缺点是要求内容能容易抽取成有意义的特征,要求特征内容有良好的结构性,并且用户的口味必须能够用内容特征形式来表达,不能显式地得到其它用户的判断情况。

协同过滤推荐

基于用户的系统过滤推荐过程

协同过滤推荐(Collaborative Filtering Recommendation)技术是推荐系统中应用最早和最为成功的技术之一。它一般采用最近邻技术,利用用户的历史喜好信息计算用户之间的距离,然后 利用目标用户的最近邻居用户对商品评价的加权评价值来预测目标用户对特定商品的喜好程度,系统从而根据这一喜好程度来对目标用户进行推荐。协同过滤最大优 点是对推荐对象没有特殊的要求,能处理非结构化的复杂对象,如音乐、电影。

用户评分矩阵

协同过滤是基于这样的假设:为一用户找到他真正感兴趣的内容的好方法是首先找到与此用户有相似兴趣的其他用户,然后将他们感兴趣的内容推荐给此用 户。其基本思想非常易于理解,在日常生活中,我们往往会利用好朋友的推荐来进行一些选择。协同过滤正是把这一思想运用到电子商务推荐系统中来,基于其他用 户对某一内容的评价来向目标用户进行推荐。

基于协同过滤的推荐系统可以说是从用户的角度来进行相应推荐的,而且是自动的即用户获得的推荐是系统从购买模式或浏览行为等隐式获得的,不需要用户努力地找到适合自己兴趣的推荐信息,如填写一些调查表格等。

和基于内容的过滤方法相比,协同过滤具有如下的优点:
1) 能够过滤难以进行机器自动内容分析的信息,如艺术品,音乐等。
2) 共享其他人的经验,避免了内容分析的不完全和不精确,并且能够基于一些复杂的,难以表述的概念(如信息质量、个人品味)进行过滤。
3) 有推荐新信息的能力。可以发现内容上完全不相似的信息,用户对推荐信息的内容事先是预料不到的。这也是协同过滤和基于内容的过滤一个较大的差别,基于内容的过滤推荐很多都是用户本来就熟悉的内容,而协同过滤可以发现用户潜在的但自己尚未发现的兴趣偏好。
4) 能够有效的使用其他相似用户的反馈信息,较少用户的反馈量,加快个性化学习的速度。

虽然协同过滤作为一种典型的推荐技术有其相当的应用,但协同过滤仍有许多的问题需要解决。最典型的问题有稀疏问题(Sparsity)和可扩展问题(Scalability)。

基于关联规则推荐

基于关联规则的推荐(Association Rule-based Recommendation)是以关联规则为基础,把已购商品作为规则头,规则体为推荐对象。关联规则挖掘可以发现不同商品在销售过程中的相关性,在零 售业中已经得到了成功的应用。管理规则就是在一个交易数据库中统计购买了商品集X的交易中有多大比例的交易同时购买了商品集Y,其直观的意义就是用户在购 买某些商品的时候有多大倾向去购买另外一些商品。比如购买牛奶的同时很多人会同时购买面包。

算法的第一步关联规则的发现最为关键且最耗时,是算法的瓶颈,但可以离线进行。其次,商品名称的同义性问题也是关联规则的一个难点。

基于效用推荐

基于效用的推荐(Utility-based Recommendation)是建立在对用户使用项目的效用情况上计算的,其核心问题是怎么样为每一个用户去创建一个效用函数,因此,用户资料模型很大 程度上是由系统所采用的效用函数决定的。基于效用推荐的好处是它能把非产品的属性,如提供商的可靠性(Vendor Reliability)和产品的可得性(Product Availability)等考虑到效用计算中。

基于知识推荐

基于知识的推荐(Knowledge-based Recommendation)在某种程度是可以看成是一种推理(Inference)技术,它不是建立在用户需要和偏好基础上推荐的。基于知识的方法因 它们所用的功能知识不同而有明显区别。效用知识(Functional Knowledge)是一种关于一个项目如何满足某一特定用户的知识,因此能解释需要和推荐的关系,所以用户资料可以是任何能支持推理的知识结构,它可以 是用户已经规范化的查询,也可以是一个更详细的用户需要的表示。

组合推荐

由于各种推荐方法都有优缺点,所以在实际中,组合推荐(Hybrid Recommendation)经常被采用。研究和应用最多的是内容推荐和协同过滤推荐的组合。最简单的做法就是分别用基于内容的方法和协同过滤推荐方法 去产生一个推荐预测结果,然后用某方法组合其结果。尽管从理论上有很多种推荐组合方法,但在某一具体问题中并不见得都有效,组合推荐一个最重要原则就是通 过组合后要能避免或弥补各自推荐技术的弱点。

在组合方式上,有研究人员提出了七种组合思路:
1)加权(Weight):加权多种推荐技术结果。
2)变换(Switch):根据问题背景和实际情况或要求决定变换采用不同的推荐技术。
3)混合(Mixed):同时采用多种推荐技术给出多种推荐结果为用户提供参考。
4)特征组合(Feature combination):组合来自不同推荐数据源的特征被另一种推荐算法所采用。
5)层叠(Cascade):先用一种推荐技术产生一种粗糙的推荐结果,第二种推荐技术在此推荐结果的基础上进一步作出更精确的推荐。
6)特征扩充(Feature augmentation):一种技术产生附加的特征信息嵌入到另一种推荐技术的特征输入中。
7)元级别(Meta-level):用一种推荐方法产生的模型作为另一种推荐方法的输入。

体系结构

编辑

服务器端推荐系统

推荐系统的体系结构研究的重要问题就是用户信息收集和用户描述文件放在什么地方,服务器还是客户机上,或者是处于二者之间的代理服务器上。

基于服务器的推荐系统结构

最初的推荐系统都是基于服务器端的推荐系统,基本结构如图。在这类推荐系统中,推荐系统与Web服务器一般共享一台硬件设备。在逻辑上,推荐系统要的用户信息收集和建模都依赖于Web服务器。

由此可知,基于服务器端的推荐系统存在的问题主要包括:

1)个性化信息的收集完全由Web服务器来完成,受到了Web服务器功能的限制。

2)增加了Web服务器的系统开销。

3)对用户的隐私有极大威胁。无论是推荐系统的管理者还是入侵推荐系统的人员都能方便地获取存放在服务器上的用户数据。由于用户的个人数据是有很高价值的,接触到用户数据的部分人会出卖用户数据或把用户数据用于非法用途。

客户端推荐系统

基于客户端推荐系统

典型的客户端个性化服务系统有斯坦福大学LIRA麻省理工学院Letizia加州大学Syskill&Webert卡内基·梅隆大学PersonalWeb-Watcher等。

基于客户端的推荐系统有如下优点:

1)由于用户的信息就在本地收集和处理,因而不但能够获取丰富准确的用户信息以构建高质量的用户模型。

2)少量甚至没有用户数据存放在服务器上,Web服务器不能访问和控制用户的数据,能比较好地保护用户的隐私。

3)用户更愿意向推荐系统提供个人信息,从而提高推荐系统的推荐性能。因为基于客户端的推荐系统中的用户数据存储在用户本地客户机上,用户对数据能够进行自行控制。

基于客户端的推荐系统有一定缺点:

1)用户描述文件的形成、推荐策略的应用都依赖于所有用户数据分析的基础上进行的,而基于客户端的推荐系统较难获取其他用户的数据,用户描述文件较难得到,协同推荐策略实施也较难,所以推荐系统要重新设计,尤其是推荐策略必须进行修改。

2)个性化推荐处理过程中用户的数据资料还需要部分的传给服务器,存在隐私泄漏的危险,需要开发安全传输平台进行数据传输。

知名团队

编辑

明尼苏达大学GroupLensJohn Riedl, Joseph A.Konstan

密西根大学(Paul Resnick

卡内基梅隆大学JaimeCallan

微软研究院(Ryen W.White

纽约大学(Alexander Tuzhilin

百分点科技团队(Baifendian

词条图册更多图册

 

词条图片(7)

 

参考资料

· 1.电子商务 .知网[引用日期2017-04-01]

· 2.信息超载 .知网[引用日期2017-04-01]

· 3.推荐系统 .知网[引用日期2017-04-01]

· 4.信息过滤 .知网[引用日期2017-04-01]

回归分析

回归分析

 编辑

本词条由科普中国百科科学词条编写与应用工作项目 审核 。

回归分析(regression analysis)是确定两种或两种以上变量间相互依赖的定量关系的一种统计分析方法。运用十分广泛,回归分析按照涉及的变量的多少,分为一元回归和多元回归分析;按照因变量的多少,可分为简单回归分析和多重回归分析;按照自变量因变量之间的关系类型,可分为线性回归分析和非线性回归分析。如果在回归分析中,只包括一个自变量和一个因变量,且二者的关系可用一条直线近似表示,这种回归分析称为一元线性回归分析。如果回归分析中包括两个或两个以上的自变量,且自变量之间存在线性相关,则称为多重线性回归分析。

中文名

回归分析

外文名

regression analysis

1

定义

2

方法

3

假定条件与内容

4

应用

5

步骤

6

注意问题

目录

00001. 1 定义

00002. 2 方法

00003. 3 假定条件与内容

00004. 4 应用

00001. 5 步骤

00002.  确定变量

00003.  建立预测模型

00004.  进行相关分析

00001.  计算预测误差

00002.  确定预测值

00003. 6 注意问题

定义

编辑

在统计学中,回归分析(regression analysis)指的是确定两种或两种以上变量间相互依赖的定量关系的一种统计分析方法。回归分析按照涉及的变量的多少,分为一元回归和多元回归分析;按照因变量的多少,可分为简单回归分析和多重回归分析;按照自变量因变量之间的关系类型,可分为线性回归分析和非线性回归分析。 [1] 

在大数据分析中,回归分析是一种预测性的建模技术,它研究的是因变量(目标)和自变量(预测器)之间的关系。这种技术通常用于预测分析,时间序列模型以及发现变量之间的因果关系。例如,司机的鲁莽驾驶与道路交通事故数量之间的关系,最好的研究方法就是回归。

方法

编辑

 

有各种各样的回归技术用于预测。这些技术主要有三个度量(自变量的个数,因变量的类型以及回归线的形状),如下图。

1. Linear Regression线性回归

它是最为人熟知的建模技术之一。线性回归通常是人们在学习预测模型时首选的技术之一。在这种技术中,因变量是连续的,自变量可以是连续的也可以是离散的,回归线的性质是线性的。

线性回归使用最佳的拟合直线(也就是回归线)在因变量(Y)和一个或多个自变量(X)之间建立一种关系。

多元线性回归可表示为Y=a+b1*X +b2*X2+ e,其中a表示截距,b表示直线的斜率,e是误差项。多元线性回归可以根据给定的预测变量(s)来预测目标变量的值。

2.Logistic Regression逻辑回归

逻辑回归是用来计算事件=Success”事件=Failure”的概率。当因变量的类型属于二元(1 / 0,真/假,是/否)变量时,我们就应该使用逻辑回归。这里,Y的值为01,它可以用下方程表示。

odds= p/ (1-p) = probability of event occurrence / probability of not event occurrence

ln(odds) = ln(p/(1-p))

logit(p) = ln(p/(1-p)) =b0+b1X1+b2X2+b3X3....+bkXk

上述式子中,p表述具有某个特征的概率。你应该会问这样一个问题:我们为什么要在公式中使用对数log呢?

因为在这里我们使用的是的二项分布(因变量),我们需要选择一个对于这个分布最佳的连结函数。它就是Logit函数。在上述方程中,通过观测样本的极大似然估计值来选择参数,而不是最小化平方和误差(如在普通回归使用的)。

3. Polynomial Regression多项式回归

对于一个回归方程,如果自变量的指数大于1,那么它就是多项式回归方程。如下方程所示:

y=a+b*x^2

在这种回归技术中,最佳拟合线不是直线。而是一个用于拟合数据点的曲线。

4. Stepwise Regression逐步回归

在处理多个自变量时,我们可以使用这种形式的回归。在这种技术中,自变量的选择是在一个自动的过程中完成的,其中包括非人为操作。

这一壮举是通过观察统计的值,如R-squaret-statsAIC指标,来识别重要的变量。逐步回归通过同时添加/删除基于指定标准的协变量来拟合模型。下面列出了一些最常用的逐步回归方法:

标准逐步回归法做两件事情。即增加和删除每个步骤所需的预测。

向前选择法从模型中最显著的预测开始,然后为每一步添加变量。

向后剔除法与模型的所有预测同时开始,然后在每一步消除最小显著性的变量。

这种建模技术的目的是使用最少的预测变量数来最大化预测能力。这也是处理高维数据集的方法之一。 [2] 

5. Ridge Regression岭回归

当数据之间存在多重共线性(自变量高度相关)时,就需要使用岭回归分析。在存在多重共线性时,尽管最小二乘法(OLS)测得的估计值不存在偏差,它们的方差也会很大,从而使得观测值与真实值相差甚远。岭回归通过给回归估计值添加一个偏差值,来降低标准误差。

在线性等式中,预测误差可以划分为 2 个分量,一个是偏差造成的,一个是方差造成的。预测误差可能会由这两者或两者中的任何一个造成。在这里,我们将讨论由方差所造成的误差。 [2] 

岭回归通过收缩参数λlambda)解决多重共线性问题。请看下面的等式:

L2=argmin||y=xβ||

  

+λ||β||

 

 

在这个公式中,有两个组成部分。第一个是最小二乘项,另一个是β-平方的λ倍,其中β是相关系数向量,与收缩参数一起添加到最小二乘项中以得到一个非常低的方差。

6. Lasso Regression套索回归

它类似于岭回归,Lasso Least Absolute Shrinkage and Selection Operator)也会就回归系数向量给出惩罚值项。此外,它能够减少变化程度并提高线性回归模型的精度。看看下面的公式:

L1=agrmin||y-xβ||

  

+λ||β||

 

Lasso 回归与Ridge回归有一点不同,它使用的惩罚函数是L1范数,而不是L2范数。这导致惩罚(或等于约束估计的绝对值之和)值使一些参数估计结果等于零。使用惩罚值越大,进一步估计会使得缩小值越趋近于零。这将导致我们要从给定的n个变量中选择变量。

如果预测的一组变量是高度相关的,Lasso 会选出其中一个变量并且将其它的收缩为零。

7.ElasticNet回归

ElasticNetLassoRidge回归技术的混合体。它使用L1来训练并且L2优先作为正则化矩阵。当有多个相关的特征时,ElasticNet是很有用的。Lasso 会随机挑选他们其中的一个,而ElasticNet则会选择两个。

 

LassoRidge之间的实际的优点是,它允许ElasticNet继承循环状态下Ridge的一些稳定性。

数据探索是构建预测模型的必然组成部分。在选择合适的模型时,比如识别变量的关系和影响时,它应该是首选的一步。比较适合于不同模型的优点,我们可以分析不同的指标参数,如统计意义的参数,R-squareAdjusted R-squareAICBIC以及误差项,另一个是Mallows’ Cp准则。这个主要是通过将模型与所有可能的子模型进行对比(或谨慎选择他们),检查在你的模型中可能出现的偏差。

交叉验证是评估预测模型最好的方法。在这里,将你的数据集分成两份(一份做训练和一份做验证)。使用观测值和预测值之间的一个简单均方差来衡量你的预测精度。

如果你的数据集是多个混合变量,那么你就不应该选择自动模型选择方法,因为你应该不想在同一时间把所有变量放在同一个模型中。

它也将取决于你的目的。可能会出现这样的情况,一个不太强大的模型与具有高度统计学意义的模型相比,更易于实现。回归正则化方法(LassoRidgeElasticNet)在高维和数据集变量之间多重共线性情况下运行良好。 [3] 

假定条件与内容

编辑

在数据分析中我们一般要对数据进行一些条件假定:

方差齐性

线性关系

效应累加

变量无测量误差

变量服从多元正态分布

观察独立

模型完整(没有包含不该进入的变量、也没有漏掉应该进入的变量)

误差项独立且服从(01)正态分布。

现实数据常常不能完全符合上述假定。因此,统计学家研究出许多的回归模型来解决线性回归模型假定过程的约束。

回归分析的主要内容为:

从一组数据出发,确定某些变量之间的定量关系式,即建立数学模型并估计其中的未知参数。估计参数的常用方法是最小二乘法

对这些关系式的可信程度进行检验。

在许多自变量共同影响着一个因变量的关系中,判断哪个(或哪些)自变量的影响是显著的,哪些自变量的影响是不显著的,将影响显著的自变量加入模型中,而剔除影响不显著的变量,通常用逐步回归、向前回归和向后回归等方法。

利用所求的关系式对某一生产过程进行预测或控制。回归分析的应用是非常广泛的,统计软件包使各种回归方法计算十分方便。

在回归分析中,把变量分为两类。一类是因变量,它们通常是实际问题中所关心的一类指标,通常用Y表示;而影响因变量取值的的另一类变量称为自变量,用X来表示。

回归分析研究的主要问题是:

1)确定YX间的定量关系表达式,这种表达式称为回归方程;

2)对求得的回归方程的可信度进行检验;

3)判断自变量X对因变量Y有无影响;

4)利用所求得的回归方程进行预测和控制。 [4] 

应用

编辑

相关分析研究的是现象之间是否相关、相关的方向和密切程度,一般不区别自变量或因变量。而回归分析则要分析现象之间相关的具体形式,确定其因果关系,并用数学模型来表现其具体关系。比如说,从相关分析中我们可以得知质量用户满意度变量密切相关,但是这两个变量之间到底是哪个变量受哪个变量的影响,影响程度如何,则需要通过回归分析方法来确定。[1] 

一般来说,回归分析是通过规定因变量和自变量来确定变量之间的因果关系,建立回归模型,并根据实测数据来求解模型的各个参数,然后评价回归模型是否能够很好的拟合实测数据;如果能够很好的拟合,则可以根据自变量作进一步预测。

例如,如果要研究质量和用户满意度之间的因果关系,从实践意义上讲,产品质量会影响用户的满意情况,因此设用户满意度为因变量,记为Y;质量为自变量,记为X。通常可以建立下面的线性关系: Y=A+BX+§

式中:AB为待定参数,A回归直线的截距;B为回归直线的斜率,表示X变化一个单位时,Y的平均变化情况;§为依赖于用户满意度的随机误差项

对于经验回归方程: y=0.857+0.836x

回归直线在y轴上的截距为0.857、斜率0.836,即质量每提高一分,用户满意度平均上升0.836分;或者说质量每提高1分对用户满意度的贡献是0.836分。

上面所示的例子是简单的一个自变量的线性回归问题,在数据分析的时候,也可以将此推广到多个自变量的多元回归,具体的回归过程和意义请参考相关的统计学书籍。此外,在SPSS的结果输出里,还可以汇报R2F检验值和T检验值。R2又称为方程的确定性系数(coefficient of determination),表示方程中变量XY的解释程度。R2取值在01之间,越接近1,表明方程中XY的解释能力越强。通常将R2乘以100%来表示回归方程解释Y变化的百分比。F检验是通过方差分析表输出的,通过显著性水平(significance level)检验回归方程的线性关系是否显著。一般来说,显著性水平在0.05以上,均有意义。当F检验通过时,意味着方程中至少有一个回归系数是显著的,但是并不一定所有的回归系数都是显著的,这样就需要通过T检验来验证回归系数的显著性。同样地,T检验可以通过显著性水平或查表来确定。在上面所示的例子中,各参数的意义如下表所示。

线性回归方程检验

指标

显著性水平

 意义

R2

0.89

质量解释了89%用户满意度的变化程度

F

276.82

0.001

回归方程的线性关系显著

T

16.64

0.001

回归方程的系数显著


示例 SIM手机用户满意度与相关变量线性回归分析

我们以SIM手机的用户满意度与相关变量的线性回归分析为例,来进一步说明线性回归的应用。从实践意义讲上,手机的用户满意度应该与产品的质量、价格和形象有关,因此我们以用户满意度为因变量,质量形象价格为自变量,作线性回归分析。利用SPSS软件的回归分析,得到回归方程如下:

用户满意度=0.008×形象+0.645×质量+0.221×价格

对于SIM手机来说,质量对其用户满意度的贡献比较大,质量每提高1分,用户满意度将提高0.645分;其次是价格,用户对价格的评价每提高1分,其满意度将提高0.221分;而形象对产品用户满意度的贡献相对较小,形象每提高1分,用户满意度仅提高0.008分。

方程各检验指标及含义如下:

指标

显著性水平

意义

R2

0.89

89%的用户满意度的变化程度

F

248.53

0.001

回归方程的线性关系显著

T(形象)

0.00

1.000

形象变量对回归方程几乎没有贡献

T(质量)

13.93

0.001

质量对回归方程有很大贡献

T(价格)

5.00

0.001

价格对回归方程有很大贡献

从方程的检验指标来看,形象对整个回归方程的贡献不大,应予以删除。所以重新做用户满意度质量价格的回归方程如下: 满意度=0.645×质量+0.221×价格

用户对价格的评价每提高1分,其满意度将提高0.221分(在本示例中,因为形象对方程几乎没有贡献,所以得到的方程与前面的回归方程系数差不多)。

方程各检验指标及含义如下:

指标

显著性水平

意义

R2

0.89

89%的用户满意度的变化程度

F

374.69

0.001

回归方程的线性关系显著

T(质量)

15.15

0.001

质量对回归方程有很大贡献

T(价格)

5.06

0.001

价格对回归方程有很大贡献

步骤

编辑

确定变量

明确预测的具体目标,也就确定了因变量。如预测具体目标是下一年度的销售量,那么销售量Y就是因变量。通过市场调查和查阅资料,寻找与预测目标的相关影响因素,即自变量,并从中选出主要的影响因素。

建立预测模型

依据自变量和因变量的历史统计资料进行计算,在此基础上建立回归分析方程,即回归分析预测模型。

进行相关分析

回归分析是对具有因果关系的影响因素(自变量)和预测对象(因变量)所进行的数理统计分析处理。只有当自变量与因变量确实存在某种关系时,建立的回归方程才有意义。因此,作为自变量的因素与作为因变量的预测对象是否有关,相关程度如何,以及判断这种相关程度的把握性多大,就成为进行回归分析必须要解决的问题。进行相关分析,一般要求出相关关系,以相关系数的大小来判断自变量和因变量的相关的程度。

计算预测误差

回归预测模型是否可用于实际预测,取决于对回归预测模型的检验和对预测误差的计算。回归方程只有通过各种检验,且预测误差较小,才能将回归方程作为预测模型进行预测。

确定预测值

利用回归预测模型计算预测值,并对预测值进行综合分析,确定最后的预测值。

注意问题

编辑

应用回归预测法时应首先确定变量之间是否存在相关关系。如果变量之间不存在相关关系,对这些变量应用回归预测法就会得出错误的结果。

正确应用回归分析预测时应注意:

用定性分析判断现象之间的依存关系;

避免回归预测的任意外推;

应用合适的数据资料;

词条图册更多图册

 

词条图片(5)

 

群体、数量遗传学

 群体

 理想群体

 无限群体

 有限群体

 孟德尔式群体

 异质群体

 同质群体

 平衡群体

 有效群体大小

 交配系统

 随机交配

 同型交配

 选型交配

 异型交配

 矫正交配

 基因库

 基因多样性

 基因流

 基因一致性

 遗传平衡

 瓶颈效应

 建立者效应

 遗传漂变

 突变压

 基因型频率

 基因频率

 哈迪-温伯格平衡

 赖特平衡

 连锁平衡

 连锁不平衡

 遗传冲刷

 遗传距离

 遗传死亡

 遗传负荷

 突变负荷

 分离负荷

 迁移负荷

 置换负荷

 致死当量

 性状趋异

其他科技名词

参考资料

· 1.孙文生.统计学.北京:中国农业出版社,2014

· 2.你应该了解的 7 种回归分析技术 freelycode[引用日期2017-05-13]

· 3.7 Types of Regression Techniques you should know!  analyticsvidhya2015-08-15[引用日期2017-04-21]

· 4.盛骤.概率论与数理统计:高等教育出版社,2010

文本挖掘

文本挖掘

 编辑

《文本挖掘》是 200908月由人民邮电出版社出版的图书,作者是学者费尔德曼。该书中涵盖了核心文本挖掘操作、文本挖掘预处理技术、分类、聚类、信息提取、信息提取的概率模型、预处理应用、可视化方法、链接分析、文本挖掘应用等内容,很好地结合了文本挖掘的理论和实践。 [1] 

    

文本挖掘

    

费尔德曼

ISBN

9787115205353

    

69

出版社

 

出版时间

 200908

    

 16

目录

00001. 1 内容简介

00002. 2 作者简介

00003. 3 文本挖掘工具

内容简介

编辑

《文本挖掘(英文版)》是一部文本挖掘领域名著,作者为世界知

 

名的权威学者。《文本挖掘(英文版)》非常适合文本挖掘、信息检索领域的研究人员和实践者阅读,也适合作为高等院校计算机及相关专业研究生的数据挖掘和知识发现等课程的教材。

作者简介

编辑

Ronen FeIdmarl,机器学习、数据挖掘和非结构化数据管理的先驱人物。以色列Barliarl大学数学与计算机科学系高级讲师、数据挖掘实验室主任,Clearforest公司(主要为企业和政府机构开发下一代文本挖掘应用)合作创始人、董事长,现在还是纽约大学斯特恩商学院的副教授。

James Sanger风险投资家,商业数据解决方案、因特网应用和IT安全产品领域公认的行业专家。他于1982年与人合伙创立了ABS Vetllures公司。此前,他是DB Capital纽约公司的常务董事他本科毕业于宾夕法尼亚大学,研究生就读于牛津大学和利物浦大学他是IEEE和美国人工智能协会(AAAI)会员。

文本挖掘工具

编辑

DMC Text FilterHYFsoft推出的纯文本抽出通用程序库,DMC Text Filter可以从各种各样的文档格式的数据中或从插入的OLE对象中,完全除掉特殊控制信息,快速抽出纯文本数据信息。便于用户实现对多种文档数据资源信息进行统一管理,编辑,检索和浏览。

DMC Text Filter采用了先进的多语言、多平台、多线程的设计理念,支持多国语言(英语,中文简体,中文繁体,日本语,韩国语),多种操作系统(WindowsSolarisLinuxIBM AIXMacintoshHP-UNIX),多种文字集合代码(GBKGB18030Big5ISO-8859-1KS X 1001Shift_JISWINDOWS31JEUC-JPISO-10646-UCS-2ISO-10646-UCS-4UTF-16UTF-8等)。提供了多种形式的API功能接口(文件格式识别函数,文本抽出函数,文件属性抽出函数,页抽出函数,设定User PasswordPDF文件的文本抽出函数等),便于用户方便使用。用户可以十分便利的将本产品组装到自己的应用程序中,进行二次开发。通过调用本产品的提供的API功能接口,实现从多种文档格式的数据中快速抽出纯文本数据。

参考资料

· 1.文本挖掘 .豆瓣[引用日期2017-06-09]

词条标签:

出版物 , 书籍

决策树

决策树

 锁定

本词条由科普中国百科科学词条编写与应用工作项目 审核 。

决策树(Decision Tree)是在已知各种情况发生概率的基础上,通过构成决策树来求取净现值的期望值大于等于零的概率,评价项目风险,判断其可行性的决策分析方法,是直观运用概率分析的一种图解法。由于这种决策分支画成图形很像一棵树的枝干,故称决策树。在机器学习中,决策树是一个预测模型,他代表的是对象属性与对象值之间的一种映射关系。Entropy = 系统的凌乱程度,使用算法ID3C4.5C5.0生成树算法使用熵。这一度量是基于信息学理论中熵的概念。

决策树是一种树形结构,其中每个内部节点表示一个属性上的测试,每个分支代表一个测试输出,每个叶节点代表一种类别。

分类树(决策树)是一种十分常用的分类方法。他是一种监管学习,所谓监管学习就是给定一堆样本,每个样本都有一组属性和一个类别,这些类别是事先确定的,那么通过学习得到一个分类器,这个分类器能够对新出现的对象给出正确的分类。这样的机器学习就被称之为监督学习。

中文名

决策树

外文名

Decision Tree

目录

00001. 1 组成

00002. 2 画法

00003. 3 决策树的剪枝

00001. 4 优点

00002. 5 缺点

00003. 6 算法

00001.  C4.5

00002.  CART

00003. 7 实例

组成

□——决策点,是对几种可能方案的选择,即最后选择的最佳方案。如果决策属于多级决策,则决策树的中间可以有多个决策点,以决策树根部的决策点为最终决策方案。 [1] 

○——状态节点,代表备选方案的经济效果(期望值),通过各状态节点的经济效果的对比,按照一定的决策标准就可以选出最佳方案。由状态节点引出的分支称为概率枝,概率枝的数目表示可能出现的自然状态数目每个分枝上要注明该状态出现的概率。 [1] 

△——结果节点,将每个方案在各种自然状态下取得的损益值标注于结果节点的右端。 [1] 

【概述来源】 [2] 

画法

机器学习中,决策树是一个预测模型;他代表的是对象属性与对象值之间的一种映射关系。树中每个节点表示某个对象,而每个分叉路径则代表的某个可能的属性值,而每个叶结点则对应从根节点到该叶节点所经历的路径所表示的对象的值。决策树仅有单一输出,若欲有复数输出,可以建立独立的决策树以处理不同输出。数据挖掘中决策树是一种经常要用到的技术,可以用于分析数据,同样也可以用来作预测。

从数据产生决策树的机器学习技术叫做决策树学习通俗说就是决策树。

一个决策树包含三种类型的节点:

00001. 

决策节点:通常用矩形框来表示

00002. 

00003. 

机会节点:通常用圆圈来表示

00004. 

00005. 

终结点:通常用三角形来表示

00006. 

决策树学习也是资料探勘中一个普通的方法。在这里,每个决策树都表述了一种树型结构,它由它的分支来对该类型的对象依靠属性进行分类。每个决策树可以依靠对源数据库的分割进行数据测试。这个过程可以递归式的对树进行修剪。 当不能再进行分割或一个单独的类可以被应用于某一分支时,递归过程就完成了。另外,随机森林分类器将许多决策树结合起来以提升分类的正确率。

决策树同时也可以依靠计算条件概率来构造。

决策树如果依靠数学的计算方法可以取得更加理想的效果。 数据库已如下所示:


(x, y) = (x1, x2, x3…, xk, y)

相关的变量 Y 表示我们尝试去理解,分类或者更一般化的结果。 其他的变量x1, x2, x3 等则是帮助我们达到目的的变量。

决策树的剪枝

剪枝是决策树停止分支的方法之一,剪枝有分预先剪枝和后剪枝两种。预先剪枝是在树的生长过程中设定一个指标,当达到该指标时就停止生长,这样做容易产生视界局限,就是一旦停止分支,使得节点N成为叶节点,就断绝了其后继节点进行的分支操作的任何可能性。不严格的说这些已停止的分支会误导学习算法,导致产生的树不纯度降差最大的地方过分靠近根节点。后剪枝中树首先要充分生长,直到叶节点都有最小的不纯度值为止,因而可以克服视界局限。然后对所有相邻的成对叶节点考虑是否消去它们,如果消去能引起令人满意的不纯度增长,那么执行消去,并令它们的公共父节点成为新的叶节点。这种合并叶节点的做法和节点分支的过程恰好相反,经过剪枝后叶节点常常会分布在很宽的层次上,树也变得非平衡。后剪枝技术的优点是克服了视界局限效应,而且无需保留部分样本用于交叉验证,所以可以充分利用全部训练集的信息。但后剪枝的计算量代价比预剪枝方法大得多,特别是在大样本集中,不过对于小样本的情况,后剪枝方法还是优于预剪枝方法的。

优点

决策树易于理解和实现,人们在在学习过程中不需要使用者了解很多的背景知识,这同时是它的能够直接体现数据的特点,只要通过解释后都有能力去理解决策树所表达的意义。 [3] 

对于决策树,数据的准备往往是简单或者是不必要的,而且能够同时处理数据型和常规型属性,在相对短的时间内能够对大型数据源做出可行且效果良好的结果。 [4] 

易于通过静态测试来对模型进行评测,可以测定模型可信度;如果给定一个观察的模型,那么根据所产生的决策树很容易推出相应的逻辑表达式。 [4] 

缺点

1)对连续性的字段比较难预测。

2)对有时间顺序的数据,需要很多预处理的工作。

3)当类别太多时,错误可能就会增加的比较快。

4)一般的算法分类的时候,只是根据一个字段来分类。 [5] 

算法

C4.5

C4.5算法继承了ID3算法的优点,并在以下几方面对ID3算法进行了改进:

1) 用信息增益率来选择属性,克服了用信息增益选择属性时偏向选择取值多的属性的不足;

2) 在树构造过程中进行剪枝;

3) 能够完成对连续属性的离散化处理;

4) 能够对不完整数据进行处理。

C4.5算法有如下优点:产生的分类规则易于理解,准确率较高。其缺点是:在构造树的过程中,需要对数据集进行多次的顺序扫描和排序,因而导致算法的低效。此外,C4.5只适合于能够驻留于内存的数据集,当训练集大得无法在内存容纳时程序无法运行。

具体算法步骤如下;

1创建节点N

2如果训练集为空,在返回节点N标记为Failure

3如果训练集中的所有记录都属于同一个类别,则以该类别标记节点N

4如果候选属性为空,则返回N作为叶节点,标记为训练集中最普通的类;

5for each 候选属性 attribute_list

6if 候选属性是连续的then

7对该属性进行离散化

8选择候选属性attribute_list中具有最高信息增益率的属性D

9标记节点N为属性D

10for each 属性D的一致值d

11节点N长出一个条件为D=d的分支

12s是训练集中D=d的训练样本的集合

13if s为空

14加上一个树叶,标记为训练集中最普通的类

15else加上一个有C4.5R - {D},Cs)返回的点

CART

背景:

分类与回归树(CART——Classification And Regression Tree)) 是一种非常有趣并且十分有效的非参数分类和回归方法。它通过构建二叉树达到预测目的。

分类与回归树CART 模型最早由Breiman 等人提出,已经在统计领域和数据挖掘技术中普遍使用。它采用与传统统计学完全不同的方式构建预测准则,它是以二叉树的形式给出,易于理解、使用和解释。由CART 模型构建的预测树在很多情况下比常用的统计方法构建的代数学预测准则更加准确,且数据越复杂、变量越多,算法的优越性就越显著。模型的关键是预测准则的构建,准确的。

定义:

分类和回归首先利用已知的多变量数据构建预测准则进而根据其它变量值对一个变量进行预测。在分类中人们往往先对某一客体进行各种测量然后利用一定的分类准则确定该客体归属那一类。例如给定某一化石的鉴定特征预测该化石属那一科、那一属甚至那一种。另外一个例子是已知某一地区的地质和物化探信息预测该区是否有矿。回归则与分类不同它被用来预测客体的某一数值而不是客体的归类。例如给定某一地区的矿产资源特征预测该区的资源量。 [6] 

实例

为了适应市场的需要,某地准备扩大电视机生产。市场预测表明:产品销路好的概率为0.7;销路差的概率为0.3。备选方案有三个:第一个方案是建设大工厂,需要投资600万元,可使用10年;如销路好,每年可赢利200万元;如销路不好,每年会亏损40万元。第二个方案是建设小工厂,需投资280万元;如销路好,每年可赢利80万元;如销路不好,每年也会赢利60万元。第三个方案也是先建设小工厂,但是如销路好,3年后扩建,扩建需投资400万元,可使用7年,扩建后每年会赢利190万元。


  各点期望:

0.7×200×10+0.3×-40×10-600(投资)=680(万元)

决策树分析 [7]

1.0×190×7-400=930(万元)

1.0×80×7=560(万元)

比较决策点4的情况可以看到,由于点930万元)与点560万元)相比,点的期望利润值较大,因此应采用扩建的方案,而舍弃不扩建的方案。把点930万元移到点4来,可计算出点的期望利润值。

0.7×80×3+0.7×930+0.3×60×3+7-280 = 719(万元)

最后比较决策点1的情况。由于点719万元)与点680万元)相比,点的期望利润值较大,因此取点而舍点。这样,相比之下,建设大工厂的方案不是最优方案,合理的策略应采用前3年建小工厂,如销路好,后7年进行扩建的方案。 [7] 

参考资料

· 1.戴淑芬 .管理学教程 :北京大学出版社,200976-79

· 2.曹赛玉.几种决策概率模型在现实生活中的应用.理论与实践理论月刊,20065

· 3.基于决策树的数据挖掘算法的应用与研究 .拓步ERP资讯网 [引用日期2013-01-16]

· 4.陈诚,基于AFS理论的模糊分类器设计大连理工大学,2010

· 5.什么是决策树? .中国机床网[引用日期2013-01-16]

· 6.Jiawei Han.数据挖掘概念与技术:机械工业出版社,2012

· 7.决策树 .云南大学[引用日期2013-01-22]

词条标签:

科学百科信息科学分类 , 中国电子学会 , 科学

支持向量机

支持向量机

 锁定

本词条由科普中国百科科学词条编写与应用工作项目 审核 。

支持向量机(Support Vector MachineSVM)Corinna CortesVapnik等于1995年首先提出的,它在解决小样本、非线性及高维模式识别中表现出许多特有的优势,并能够推广应用到函数拟合等其他机器学习问题中。

在机器学习中,支持向量机(SVM,还支持矢量网络)是与相关的学习算法有关的监督学习模型,可以分析数据,识别模式,用于分类和回归分析。

中文名

支持向量机

外文名

Support Vector Machine

    

SVM

提出时间

1995

提出者

Corinna CortesVapnik

相关内容

学习算法,监督学习

目录

00001. 1 简介

00002. 2 总体概述:

00003. 3 有关介绍:

00001. 4 相关知识

00002. 5 支持原因

00003. 6 支持向量概述

00001. 7 相关技术支持

简介

支持向量机方法是建立在统计学习理论的VC理论和结构风险最小原理基础上的,根据有限的样本信息在模型的复杂性(即对特定训练样本的学习精度)和学习能力(即无错误地识别任意样本的能力)之间寻求最佳折中,以求获得最好的推广能力 

总体概述:

在机器学习中,支持向量机(SVM,还支持矢量网络)是与相关的学习算法有关的监督学习模型,可以分析数据,识别模式,用于分类和回归分析。给定一组训练样本,每个标记为属于两类,一个SVM训练算法建立了一个模型,分配新的实例为一类或其他类,使其成为非概率二元线性分类。一个SVM模型的例子,如在空间中的点,映射,使得所述不同的类别的例子是由一个明显的差距是尽可能宽划分的表示。新的实施例则映射到相同的空间中,并预测基于它们落在所述间隙侧上属于一个类别。

除了进行线性分类,支持向量机可以使用所谓的核技巧,它们的输入隐含映射成高维特征空间中有效地进行非线性分类。

有关介绍:

更正式地说,一个支持向量机的构造一个超平面,或在高或无限维空间,其可以用于分类,回归,或其它任务中设定的超平面的。直观地,一个良好的分离通过具有到任何类(所谓官能余量)的最接近的训练数据点的最大距离的超平面的一般实现中,由于较大的裕度下分类器的泛化误差。

而原来的问题可能在一个有限维空间中所述,经常发生以鉴别集是不是在该空间线性可分。出于这个原因,有人建议,在原始有限维空间映射到一个高得多的立体空间,推测使分离在空间比较容易。保持计算负荷合理,使用支持向量机计划的映射被设计成确保在点积可在原空间中的变量而言容易地计算,通过定义它们中选择的核函数kxy)的计算以适应的问题。

在高维空间中的超平面被定义为一组点的点积与该空间中的向量是恒定的。限定的超平面的载体可被选择为线性组合与参数\alpha_i中发生的数据的基础上的特征向量的图像。这种选择一个超平面,该点中的x的特征空间映射到超平面是由关系定义:\字型\sum_i\alpha_ikx_i中,x=\mathrm{常数}。注意,如果kxy)变小为y的增长进一步远离的x,在求和的每一项测量测试点x的接近程度的相应数据基点x_i的程度。以这种方式,内核上面的总和可以被用于测量各个测试点的对数据点始发于一个或另一个集合中的要被鉴别的相对接近程度。注意一个事实,即设定点的x映射到任何超平面可以相当卷积的结果,使集未在原始空间凸出于各之间复杂得多歧视。

相关知识

我们通常希望分类的过程是一个机器学习的过程。这些数据点是n维实空间中的点。我们希望能够把这些点通过一个n-1维的超平面分开。通常这个被称为线性分类器。有很多分类器都符合这个要求。但是我们还希望找到分类最佳的平面,即使得属于两个不同类的数据点间隔最大的那个面,该面亦称为最大间隔超平面。如果我们能够找到这个面,那么这个分类器就称为最大间隔分类器。

支持原因

支持向量机将向量映射到一个更高维的空间里,在这个空间里建立有一个最大间隔超平面。在分开数据的超平面的两边建有两个互相平行的超平面。建立方向合适的分隔超平面使两个与之平行的超平面间的距离最大化。其假定为,平行超平面间的距离或差距越大,分类器的总误差越小。

一个极好的指南是C.J.C Burges的《模式识别支持向量机指南》。

支持向量概述

所谓支持向量是指那些在间隔区边缘的训练样本点。 这里的机(machine,机器)实际上是一个算法。在机器学习领域,常把一些算法看做是一个机器。

支持向量机(Support vector machinesSVM)神经网络类似,都是学习型的机制,但与神经网络不同的是SVM使用的是数学方法和优化技术。

相关技术支持

支持向量机是由Vapnik领导的AT&T Bell实验室研究小组在1995年提出的一种新的非常有潜力的分类技术,SVM是一种基于统计学习理论的模式识别方法,主要应用于模式识别领域。由于当时这些研究尚不十分完善,在解决模式识别问题中往往趋于保守,且数学上比较艰涩,这些研究一直没有得到充分的重视。

直到90年代,统计学习理论 (Statistical Learning TheorySLT)的实现和由于神经网络等较新兴的机器学习方法的研究遇到一些重要的困难,比如如何确定网络结构的问题、过学习与欠学习问题、局部极小点问题等,使得SVM迅速发展和完善,在解决小样本、非线性及高维模式识别问题中表现出许多特有的优势,并能够推广应用到函数拟合等其他机器学习问题中。从此迅速的发展起来,已经在许多领域(生物信息学,文本和手写识别等)都取得了成功的应用。

地球物理反演当中解决非线性反演也有显著成效,例如(支持向量机在预测地下水涌水量问题等)。已知该算法被应用的主要有:石油测井中利用测井资料预测地层孔隙度粘粒含量、天气预报工作等。

支持向量机中的一大亮点是在传统的最优化问题中提出了对偶理论,主要有最大最小对偶及拉格朗日对偶。

SVM的关键在于核函数。低维空间向量集通常难于划分,解决的方法是将它们映射到高维空间。但这个办法带来的困难就是计算复杂度的增加,而核函数正好巧妙地解决了这个问题。也就是说,只要选用适当的核函数,就可以得到高维空间的分类函数。在SVM理论中,采用不同的核函数将导致不同的SVM算法。

在确定了核函数之后,由于确定核函数的已知数据也存在一定的误差,考虑到推广性问题,因此引入了松弛系数以及惩罚系数两个参变量来加以校正。在确定了核函数基础上,再经过大量对比实验等将这两个系数取定,该项研究就基本完成,适合相关学科或业务内应用,且有一定能力的推广性。当然误差是绝对的,不同学科、不同专业的要求不一。

支持向量机的理解需要数据挖掘或机器学习的相关背景知识,在没有背景知识的情况下,可以先将支持向量机看作简单分类工具,再进一步引入核函数进行理解。 [1] 

猜你喜欢

转载自blog.csdn.net/qq_42603157/article/details/80969458