当前位置:首页 » 操作系统 » java堆算法

java堆算法

发布时间: 2023-12-24 11:11:37

㈠ JVM垃圾回收的“三色标记算法”实现,内容太干

三色标记法是一种垃圾回收法,它可以让JVM不发生或仅短时间发生STW(Stop The World),从而达到清除JVM内存垃圾的目的。JVM中的 CMS、G1垃圾回收器 所使用垃圾回收算法即为三色标记法。

三色标记法将对象的颜色分为了黑、灰、白,三种颜色。

白色 :该对象没有被标记过。(对象垃圾)

灰色 :该对象已经被标记过了,但该对象下的属性没有全被标记完。(GC需要从此对象中去寻找垃圾)

黑色 :该对象已经被标记过了,且该对象下的属性也全部都被标记过了。(程序所需要的对象)

从我们main方法的根对象(JVM中称为GC Root)开始沿着他们的对象向下查找,用黑灰白的规则,标记出所有跟GC Root相连接的对象,扫描一遍结束后,一般需要进行一次短暂的STW(Stop The World),再次进行扫描,此时因为黑色对象的属性都也已经被标记过了,所以只需找出灰色对象并顺着继续往下标记(且因为大部分的标记工作已经在第一次并发的时候发生了,所以灰色对象数量会很少,标记时间也会短很多), 此时程序继续执行,GC线程扫描所有的内存,找出扫描之后依旧被标记为白色的对象(垃圾),清除。

具体流程:

在JVM虚拟机中有两种常见垃圾回收器使用了该算法:CMS(Concurrent Mark Sweep)、G1(Garbage First) ,为了解决三色标记法对对象漏标问题各自有各自的法:

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的java应用集中在互联网网站或者基于浏览器的B/S系统的服务端上,这类应用通常都会较为关注服务的响应速度,希望系统停顿时间尽可能短,以给用户带来良好的交互体验。CMS收集器就非常符合这类应用的需求(但是实际由于某些问题,很少有使用CMS作为主要垃圾回收器的)。

从名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于标记-清除算法实现的,它的运作过程相对于前面几种收集器来说要更复杂一些,整个过程分为四个步骤,包括:1)初始标记(CMS initial mark) 2)并发标记(CMS concurrent mark) 3)重新标记(CMS remark) 4)并发清除(CMS concurrent sweep)

其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GCRoots能直接关联到的对象,速度很快;

并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行;

重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短;

最后是并发清除阶段,清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。由于在整个过程中耗时最长的并发标记和并发清除阶段中,垃圾收集器线程都可以与用户线程一起工作,所以从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。

在应对漏标问题时,CMS使用了增量更新(Increment Update)方法来做:

在一个未被标记的对象(白色对象)被重新引用后, 引用它的对象若为黑色则要变成灰色,在下次二次标记时让GC线程继续标记它的属性对象

但是就算是这样,其仍然是存在漏标的问题:

G1(Garbage First)物理内存不再分代,而是由一块一块的Region组成,但是逻辑分代仍然存在。G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。

Region中还有一类特殊的Humongous区域,专门用来存储大对象。G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象。每个Region的大小可以通过参数-XX:G1HeapRegionSize设定,取值范围为1MB~32MB,且应为2的N次幂。而对于那些超过了整个Region容量的超级大对象,将会被存放在N个连续的Humongous Region之中,G1的大多数行为都把Humongous Region作为老年代的一部分来进行看待,如图所示

Card Table(多种垃圾回收器均具备)

RSet(Remembered Set)

是辅助GC过程的一种结构,典型的空间换时间工具,和Card Table有些类似。

后面说到的CSet(Collection Set)也是辅助GC的,它记录了GC要收集的Region集合,集合里的Region可以是任意年代的。

在GC的时候,对于old->young和old->old的跨代对象引用,只要扫描对应的CSet中的RSet即可。逻辑上说每个Region都有一个RSet,RSet记录了其他Region中的对象引用本Region中对象的关系,属于points-into结构(谁引用了我的对象)。

而Card Table则是一种points-out(我引用了谁的对象)的结构,每个Card 覆盖一定范围的Heap(一般为512Bytes)。G1的RSet是在Card Table的基础上实现的:每个Region会记录下别的Region有指向自己的指针,并标记这些指针分别在哪些Card的范围内。这个RSet其实是一个Hash Table,Key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index。每个Region中都有一个RSet,记录其他Region到本Region的引用信息;使得垃圾回收器不需要扫描整个堆找到谁引用当前分区中的对象,只需要扫描RSet即可。

CSet(Collection Set)

一组可被回收的分区Region的集合, 是多个对象的集合内存区域。

新生代与老年代的比例

5% - 60%,一般不使用手工指定,因为这是G1预测停顿时间的基准,这地方简要说明一下,G1可以指定一个预期的停顿时间,然后G1会根据你设定的时间来动态调整年轻代的比例,例如时间长,就将年轻代比例调小,让YGC尽早行。

SATB(Snapshot At The Beginning), 在应对漏标问题时,G1使用了SATB方法来做,具体流程:

因为SATB在重新标记环节只需要去重新扫描那些被推到堆栈中的引用,并配合Rset来判断当前对象是否被引用来进行回收;

并且在最后G1并不会选择回收所有垃圾对象,而是根据Region的垃圾多少来判断与预估回收价值(指回收的垃圾与回收的STW时间的一个预估值),将一个或者多个Region放到CSet中,最后将这些Region中的存活对象压缩并复制到新的Region中,清空原来的Region。

会,当内存满了的时候就会进行Full GC;且JDK10之前的Full GC,为单线程的,所以使用G1需要避免Full GC的产生。

解决方案:

㈡ java问题

只要加到比较方法之中就可以
如:比较方法如下
for(int i=0;i<100;i++)
for(int j=i;j<100;j++){
if(array[i]>array[j]){
int temp=array[i];
array[i]=array[j];
array[j]=temp;
}
}
把::
比较次数compare_count、交换次数exchange_count、探测次数probe_count)加到里面就可以

for(int i=0,compare_count=0;i<100;i++)
for(int j=i;j<100;j++){
if(array[i]>array[j]){
compare_count++;
int temp=array[i];
array[i]=array[j];
array[j]=temp;
exchange_count++;
}
}

就可以了
各种排序方法的综合比较

一、时间性能

按平均的时间性能来分,有三类排序方法:
时间复杂度为O(nlogn)的方法有:快速排序、堆排序和归并排序,其中以快速排序为最好;

时间复杂度为O(n2)的有:直接插入排序、起泡排序和简单选择排序,其中以直接插入为最好,特别是对那些对关键字近似有序的记录序列尤为如此;

时间复杂度为O(n)的排序方法只有,基数排序。

当待排记录序列按关键字顺序有序时,直接插入排序和起泡排序能达到O(n)的时间复杂度;而对于快速排序而言,这是最不好的情况,此时的时间性能蜕化为O(n2),因此是应该尽量避免的情况。
简单选择排序、堆排序和归并排序的时间性能不随记录序列中关键字的分布而改变。
二、空间性能

指的是排序过程中所需的辅助空间大小。

1. 所有的简单排序方法(包括:直接插入、起泡和简单选择)和堆排序的空间复杂度为O(1);

2. 快速排序为O(logn ),为栈所需的辅助空间;

3. 归并排序所需辅助空间最多,其空间复杂度为O(n );

4.链式基数排序需附设队列首尾指针,则空间复杂度为O(rd )。

三、排序方法的稳定性能

1. 稳定的排序方法指的是,对于两个关键字相等的记录,它们在序列中的相对位置,在排序之前和经过排序之后,没有改变。

2. 当对多关键字的记录序列进行LSD方法排序时,必须采用稳定的排序方法。

3. 对于不稳定的排序方法,只要能举出一个实例说明即可。

4. 快速排序和堆排序是不稳定的排序方法。

四、关于“排序方法的时间复杂度的下限”

本章讨论的各种排序方法,除基数排序外,其它方法都是基于“比较关键字”进行排序的排序方法,可以证明,这类排序法可能达到的最快的时间复杂度为O(n logn )。(基数排序不是基于“比较关键字”的排序方法,所以它不受这个限制)。

可以用一棵判定树来描述这类基于“比较关键字”进行排序的排序方法。

例如,对三个关键字进行排序的判定树如下:

描述排序的判定树有两个特点:

1.树上的每一次“比较”都是必要的;

2.树上的叶子结点包含所有可能情况。

则由上图所示“判定树的深度为4”可以推出“至多进行三次比较”即可完成对三个关键字的排序。反过来说,由此判定树可见,考虑最坏情况,“至少要进行三次比较”才能完成对三个关键字的排序。

对三个关键字进行排序的判定树深度是唯一的。即无论按什么先后顺序去进行比较,所得判定树的深度都是3。

当关键字的个数超过3之后,不同的排序方法其判定树的深度不同。例如,对4个关键字进行排序时,直接插入的判定树的深度为6, 而折半插入的判定树的深度为5。

可以证明,对4个关键字进行排序,至少需进行5次比较。因为,4个关键字排序的结果有4!=24种可能,即排序的判定树上必须有24个叶子结点,其深度的最小值为6。

一般情况下,对n个关键字进行排序,可能得到的结果有n! 种,由于含n! 个叶子结点的二叉树的深度不小于 , 则对n个关键字进行排序的比较次数至少是

。利用斯蒂林近似公式

所以,基于“比较关键字”进行排序的排序方法,可能达到的最快的时间复杂度为O(n logn )。

快速排序是对冒泡排序的一种改进。它的基本思想是:通过一躺排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一不部分的所有数据都要小,然后再按次方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

假设要排序的数组是A[1]……A[N],首先任意选取一个数据(通常选用第一个数据)作为关键数据,然后将所有比它的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一躺快速排序。一躺快速排序的算法是:

1)、设置两个变量I、J,排序开始的时候I:=1,J:=N;

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

3)、从J开始向前搜索,即由后开始向前搜索(J:=J-1),找到第一个小于X的值,两者交换;

4)、从I开始向后搜索,即由前开始向后搜索(I:=I+1),找到第一个大于X的值,两者交换;

5)、重复第3、4步,直到I=J;

例如:待排序的数组A的值分别是:(初始关键数据X:=49)

A[1] A[2] A[3] A[4] A[5] A[6] A[7]:

49 38 65 97 76 13 27

进行第一次交换后: 27 38 65 97 76 13 49

( 按照算法的第三步从后面开始找

进行第二次交换后: 27 38 49 97 76 13 65

( 按照算法的第四步从前面开始找>X的值,65>49,两者交换,此时I:=3 )

进行第三次交换后: 27 38 13 97 76 49 65

( 按照算法的第五步将又一次执行算法的第三步从后开始找

进行第四次交换后: 27 38 13 49 76 97 65

( 按照算法的第四步从前面开始找大于X的值,97>49,两者交换,此时J:=4 )

此时再执行第三不的时候就发现I=J,从而结束一躺快速排序,那么经过一躺快速排序之后的结果是:27 38 13 49 76 97 65,即所以大于49的数全部在49的后面,所以小于49的数全部在49的前面。

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

初始状态 {49 38 65 97 76 13 27}

进行一次快速排序之后划分为 {27 38 13} 49 {76 97 65}

分别对前后两部分进行快速排序 {13} 27 {38}

结束 结束 {49 65} 76 {97}

49 {65} 结束

结束

图6 快速排序全过程

1)、设有N(假设N=10)个数,存放在S数组中;
2)、在S[1。。N]中任取一个元素作为比较基准,例如取T=S[1],起目的就是在定出T应在排序结果中的位置K,这个K的位置在:S[1。。K-1]<=S[K]<=S[K+1..N],即在S[K]以前的数都小于S[K],在S[K]以后的数都大于S[K];

3)、利用分治思想(即大化小的策略)可进一步对S[1。。K-1]和S[K+1。。N]两组数据再进行快速排序直到分组对象只有一个数据为止。 1 2 3 4 5 6 7 8 9 10

如具体数据如下,那么第一躺快速排序的过程是:

数组下标:

45 36 18 53 72 30 48 93 15 36

5) 36 36 18 15 30 45 48 93 72 534) 36 36 18 15 45 30 48 93 72 533) 36 36 18 15 72 30 48 93 45 532) 36 36 18 45 72 30 48 93 15 53

program kuaisu(input,output);
const n=10;
var
s:array[1..10] of integer;
k,l,m:integer;

procere qsort(lx,rx:integer);
var
I,j,t:integer;
Begin
I:lx;j:rx;t:s[I];
Repeat
While (s[j]>t) and (j>I) do
Begin
k:=k+1;
j:=j-1
end;
if I<j then
begin
s[I]:=s[j];I:=I+1;l:=l+1;
while (s[I]<t) and (I<j) do
begin
k:=k+1;
I:=I+1
End;
If I<j then
begin
S[j]:=s[I];j:=j-1;l:=l+1;
End;
End;
Until I=j;
S[I]:=t;I:=I+1;j:=j-1;l:=l+1;
If lx<j then qsort(lx,j);
If I<rx then qsort(I,rx)
End;{过程qsort结束}

Begin
Writeln('input 10 integer num:');
For m:=1 to n do read(s[m]);
K:=0;l:=0;
Qsort(l,n);
Writeln('排序后结果是:');
For m:=1 to n do write(s[m]:4)
End.

通过一躺排序将45放到应该放的位置K,这里K=6,那么再对S[1。。5]和S[6。。10]分别进行快速排序。程序代码如下:<49,两者交换,此时J:=6>

㈢ java十大算法

算法一:快速排序算法
快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要Ο(n log n)次比较。在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(n log n) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。

快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。

算法步骤:

1 从数列中挑出一个元素,称为 "基准"(pivot),

2 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。

3 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

算法二:堆排序算法
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

堆排序的平均时间复杂度为Ο(nlogn) 。

算法步骤:

创建一个堆H[0..n-1]

把堆首(最大值)和堆尾互换

3. 把堆的尺寸缩小1,并调用shift_down(0),目的是把新的数组顶端数据调整到相应位置

4. 重复步骤2,直到堆的尺寸为1

算法三:归并排序
归并排序(Merge sort,台湾译作:合并排序)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

算法步骤:

1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列

2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置

3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置

4. 重复步骤3直到某一指针达到序列尾

5. 将另一序列剩下的所有元素

㈣ java垃圾是怎么回收的,回收算法

Java ,C#语言与C/C++语言一个很大的区别是java与C#具有自动垃圾回收机制。C++程序员经常需要绞尽脑汁的分析哪里出现了内存泄漏。而在java,C#中,虽然有时也会出现内存泄漏,但大部分情况下程序员不需要考虑对象或者数据何时需要被销毁。因此程序员不会因为错误的释放内存而导致程序崩溃。垃圾回收的缺点是加大了程序的负担,有可能影响程序的性能。
1.垃圾收集器的主要功能有
(1) 定期发现那些对象不再被引用,并把这些对象占据的堆空间释放出来。
(2) 类似于操作系统的内存管理,垃圾收集器还需要处理由于对象动态生成与销毁产生的堆碎块,以便更有效的利用虚拟机内存。
2.区分活动对象与垃圾的算法
(1)引用计数法
堆中每一个对象都有一个引用计数。当新创建一个对象,或者有变量被赋值为这个对象的引用,则这个对象的引用计数加1;当一个对象的引用超过生存期或者被设置一个新的值时,这个对象的引用计数减1。当对象的引用计数变为0时,就可以被当作垃圾收集。
这种方法的好处是垃圾收集较快,适用于实时环境。缺点是这种方法无法监测出循环引用。例如对象A引用对象B,对象B也引用对象A,则这两个对象可能无法被垃圾收集器收集。因此这种方法是垃圾收集的早期策略,现在很少使用。
(2)跟踪法
这种方法把每个对象看作图中一个节点,对象之间的引用关系为图中各节点的邻接关系。垃圾收集器从一个或数个根结点遍历对象图,如果有些对象节点永远无法到达,则这个对象可以被当作垃圾回收。
容易发现,这种方法可以检测出循环引用,避免了引用计数法的缺点,较为常用。
3.常用垃圾回收机制
(1)标记-清除收集器
这种收集器首先遍历对象图并标记可到达的对象,然后扫描堆栈以寻找未标记对象并释放它们的内存。这种收集器一般使用单线程工作并停止其他操作。
(2)标记-压缩收集器
有时也叫标记-清除-压缩收集器,与标记-清除收集器有相同的标记阶段。在第二阶段,则把标记对象复制到堆栈的新域中以便压缩堆栈。这种收集器也停止其他操作。
(3)复制收集器
这种收集器将堆栈分为两个域,常称为半空间。每次仅使用一半的空间,虚拟机生成的新对象则放在另一半空间中。垃圾回收器运行时,它把可到达对象复制到另一半空间,没有被复制的的对象都是不可达对象,可以被回收。这种方法适用于短生存期的对象,持续复制长生存期的对象由于多次拷贝,导致效率降低。缺点是只有一半的虚拟机空间得到使用。
(4)增量收集器
增量收集器把堆栈分为多个域,每次仅从一个域收集垃圾。这会造成较小的应用程序中断。
(5)分代收集器
这种收集器把堆栈分为两个或多个域,用以存放不同寿命的对象。虚拟机生成的新对象一般放在其中的某个域中。过一段时间,继续存在的对象将获得使用期并转入更长寿命的域中。分代收集器对不同的域使用不同的算法以优化性能。这样可以减少复制对象的时间。
(6)并发收集器
并发收集器与应用程序同时运行。这些收集器在某点上(比如压缩时)一般都不得不停止其他操作以完成特定的任务,但是因为其他应用程序可进行其他的后台操作,所以中断其他处理的实际时间大大降低。
(7)并行收集器
并行收集器使用某种传统的算法并使用多线程并行的执行它们的工作。在多CPU机器上使用多线程技术可以显着的提高java应用程序的可扩展性。
(8)自适应收集器
根据程序运行状况以及堆的使用状况,自动选一种合适的垃圾回收算法。这样可以不局限与一种垃圾回收算法。
4. 火车算法
垃圾收集算法一个很大的缺点就是难以控制垃圾回收所占用的CPU时间,以及何时需要进行垃圾回收。火车算法是分代收集器所用的算法,目的是在成熟对象空间中提供限定时间的渐进收集。目前应用于SUN公司的Hotspot虚拟机上。
在火车算法中,内存被分为块,多个块组成一个集合。为了形象化,一节车厢代表一个块,一列火车代表一个集合,见图一
注意每个车厢大小相等,但每个火车包含的车厢数不一定相等。垃圾收集以车厢为单位,收集顺序依次为1.1,1.2,1.3,1.4,2.1,2.2,2.3,3.1,3.2,3.3。这个顺序也是块被创建的先后顺序。
垃圾收集器先从块1.1开始扫描直到1.4,如果火车1四个块中的所有对象没有被火车2和火车3的对象引用,而只有火车1内部的对象相互引用,则整个火车1都是垃圾,可以被回收。
如图二,车厢1.1中有对象A和对象B,1.3中有对象C,1.4中有对象D,车厢2.2中有对象E,车厢3.3中有对象F。在火车1中,对象C引用对象A,对象B引用对象D,可见,火车2和火车3没有引用火车1的对象,则整个火车1都是垃圾。
如果火车1中有对象被其它火车引用,见图三,扫描车厢1.1时发现对象A被火车2中的E引用,则将对象A从车厢1.1转移到车厢2.2,然后扫描A引用的对象D,把D也转移到车厢2.2,然后扫描D,看D是否引用其它对象,如果引用了其它对象则也要转移,依次类推。扫描完火车1的所有对象后,剩下的没有转移的对象都是垃圾,可以把整个火车1都作为垃圾回收。注意如果在转移时,如果车厢2.2空间满了,则要在火车2末尾开辟新的车厢2.4,将新转移的对象都放到2.4,即火车的尾部)
补充说明:垃圾回收器一次只扫描一个车厢。图三中的对象B与C并不是立即被回收,而是先会被转移到火车1的尾部车厢。即扫描完1.1后,B被转移到火车1尾部,扫描完1.3后,C被转移到车尾。等垃圾收集器扫描到火车1尾部时,如果仍然没有外部对象引用它们,则B和C会被收集。
火车算法最大的好处是它可以保证大的循环结构可以被完全收集,因为成为垃圾的循环结构中的对象,无论多大,都会被移入同一列火车,最终一起被收集。还有一个好处是这种算法在大多数情况下可以保证一次垃圾收集所耗时间在一定限度之内,因为一次垃圾回收只收集一个车厢,而车厢的大小是有限度的。

㈤ 数据结构 java开发中常用的排序算法有哪些

排序算法有很多,所以在特定情景中使用哪一种算法很重要。为了选择合适的算法,可以按照建议的顺序考虑以下标准:
(1)执行时间
(2)存储空间
(3)编程工作
对于数据量较小的情形,(1)(2)差别不大,主要考虑(3);而对于数据量大的,(1)为首要。

主要排序法有:
一、冒泡(Bubble)排序——相邻交换
二、选择排序——每次最小/大排在相应的位置
三、插入排序——将下一个插入已排好的序列中
四、壳(Shell)排序——缩小增量
五、归并排序
六、快速排序
七、堆排序
八、拓扑排序

一、冒泡(Bubble)排序

----------------------------------Code 从小到大排序n个数------------------------------------
void BubbleSortArray()
{
for(int i=1;i<n;i++)
{
for(int j=0;i<n-i;j++)
{
if(a[j]>a[j+1])//比较交换相邻元素
{
int temp;
temp=a[j]; a[j]=a[j+1]; a[j+1]=temp;
}
}
}
}
-------------------------------------------------Code------------------------------------------------
效率 O(n²),适用于排序小列表。

二、选择排序
----------------------------------Code 从小到大排序n个数--------------------------------
void SelectSortArray()
{
int min_index;
for(int i=0;i<n-1;i++)
{
min_index=i;
for(int j=i+1;j<n;j++)//每次扫描选择最小项
if(arr[j]<arr[min_index]) min_index=j;
if(min_index!=i)//找到最小项交换,即将这一项移到列表中的正确位置
{
int temp;
temp=arr[i]; arr[i]=arr[min_index]; arr[min_index]=temp;
}
}
}
-------------------------------------------------Code-----------------------------------------
效率O(n²),适用于排序小的列表。

三、插入排序
--------------------------------------------Code 从小到大排序n个数-------------------------------------
void InsertSortArray()
{
for(int i=1;i<n;i++)//循环从第二个数组元素开始,因为arr[0]作为最初已排序部分
{
int temp=arr[i];//temp标记为未排序第一个元素
int j=i-1;
while (j>=0 && arr[j]>temp)/*将temp与已排序元素从小到大比较,寻找temp应插入的位置*/
{
arr[j+1]=arr[j];
j--;
}
arr[j+1]=temp;
}
}
------------------------------Code--------------------------------------------------------------
最佳效率O(n);最糟效率O(n²)与冒泡、选择相同,适用于排序小列表
若列表基本有序,则插入排序比冒泡、选择更有效率。

四、壳(Shell)排序——缩小增量排序
-------------------------------------Code 从小到大排序n个数-------------------------------------
void ShellSortArray()
{
for(int incr=3;incr<0;incr--)//增量递减,以增量3,2,1为例
{
for(int L=0;L<(n-1)/incr;L++)//重复分成的每个子列表
{
for(int i=L+incr;i<n;i+=incr)//对每个子列表应用插入排序
{
int temp=arr[i];
int j=i-incr;
while(j>=0&&arr[j]>temp)
{
arr[j+incr]=arr[j];
j-=incr;
}
arr[j+incr]=temp;
}
}
}
}
--------------------------------------Code-------------------------------------------
适用于排序小列表。
效率估计O(nlog2^n)~O(n^1.5),取决于增量值的最初大小。建议使用质数作为增量值,因为如果增量值是2的幂,则在下一个通道中会再次比较相同的元素。
壳(Shell)排序改进了插入排序,减少了比较的次数。是不稳定的排序,因为排序过程中元素可能会前后跳跃。

五、归并排序
----------------------------------------------Code 从小到大排序---------------------------------------
void MergeSort(int low,int high)
{
if(low>=high) return;//每个子列表中剩下一个元素时停止
else int mid=(low+high)/2;/*将列表划分成相等的两个子列表,若有奇数个元素,则在左边子列表大于右侧子列表*/
MergeSort(low,mid);//子列表进一步划分
MergeSort(mid+1,high);
int [] B=new int [high-low+1];//新建一个数组,用于存放归并的元素
for(int i=low,j=mid+1,k=low;i<=mid && j<=high;k++)/*两个子列表进行排序归并,直到两个子列表中的一个结束*/
{
if (arr[i]<=arr[j];)
{
B[k]=arr[i];
I++;
}
else
{ B[k]=arr[j]; j++; }
}
for( ;j<=high;j++,k++)//如果第二个子列表中仍然有元素,则追加到新列表
B[k]=arr[j];
for( ;i<=mid;i++,k++)//如果在第一个子列表中仍然有元素,则追加到新列表中
B[k]=arr[i];
for(int z=0;z<high-low+1;z++)//将排序的数组B的 所有元素复制到原始数组arr中
arr[z]=B[z];
}
-----------------------------------------------------Code---------------------------------------------------
效率O(nlogn),归并的最佳、平均和最糟用例效率之间没有差异。
适用于排序大列表,基于分治法。

六、快速排序
------------------------------------Code--------------------------------------------
/*快速排序的算法思想:选定一个枢纽元素,对待排序序列进行分割,分割之后的序列一个部分小于枢纽元素,一个部分大于枢纽元素,再对这两个分割好的子序列进行上述的过程。*/ void swap(int a,int b){int t;t =a ;a =b ;b =t ;}
int Partition(int [] arr,int low,int high)
{
int pivot=arr[low];//采用子序列的第一个元素作为枢纽元素
while (low < high)
{
//从后往前栽后半部分中寻找第一个小于枢纽元素的元素
while (low < high && arr[high] >= pivot)
{
--high;
}
//将这个比枢纽元素小的元素交换到前半部分
swap(arr[low], arr[high]);
//从前往后在前半部分中寻找第一个大于枢纽元素的元素
while (low <high &&arr [low ]<=pivot )
{
++low ;
}
swap (arr [low ],arr [high ]);//将这个枢纽元素大的元素交换到后半部分
}
return low ;//返回枢纽元素所在的位置
}
void QuickSort(int [] a,int low,int high)
{
if (low <high )
{
int n=Partition (a ,low ,high );
QuickSort (a ,low ,n );
QuickSort (a ,n +1,high );
}
}
----------------------------------------Code-------------------------------------
平均效率O(nlogn),适用于排序大列表。
此算法的总时间取决于枢纽值的位置;选择第一个元素作为枢纽,可能导致O(n²)的最糟用例效率。若数基本有序,效率反而最差。选项中间值作为枢纽,效率是O(nlogn)。
基于分治法。

七、堆排序
最大堆:后者任一非终端节点的关键字均大于或等于它的左、右孩子的关键字,此时位于堆顶的节点的关键字是整个序列中最大的。
思想:
(1)令i=l,并令temp= kl ;
(2)计算i的左孩子j=2i+1;
(3)若j<=n-1,则转(4),否则转(6);
(4)比较kj和kj+1,若kj+1>kj,则令j=j+1,否则j不变;
(5)比较temp和kj,若kj>temp,则令ki等于kj,并令i=j,j=2i+1,并转(3),否则转(6)
(6)令ki等于temp,结束。
-----------------------------------------Code---------------------------
void HeapSort(SeqIAst R)

{ //对R[1..n]进行堆排序,不妨用R[0]做暂存单元 int I; BuildHeap(R); //将R[1-n]建成初始堆for(i=n;i>1;i--) //对当前无序区R[1..i]进行堆排序,共做n-1趟。{ R[0]=R[1]; R[1]=R[i]; R[i]=R[0]; //将堆顶和堆中最后一个记录交换 Heapify(R,1,i-1); //将R[1..i-1]重新调整为堆,仅有R[1]可能违反堆性质 } } ---------------------------------------Code--------------------------------------

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

堆排序的最坏时间复杂度为O(nlgn)。堆排序的平均性能较接近于最坏性能。 由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。 堆排序是就地排序,辅助空间为O(1), 它是不稳定的排序方法。

堆排序与直接插入排序的区别:
直接选择排序中,为了从R[1..n]中选出关键字最小的记录,必须进行n-1次比较,然后在R[2..n]中选出关键字最小的记录,又需要做n-2次比较。事实上,后面的n-2次比较中,有许多比较可能在前面的n-1次比较中已经做过,但由于前一趟排序时未保留这些比较结果,所以后一趟排序时又重复执行了这些比较操作。
堆排序可通过树形结构保存部分比较结果,可减少比较次数。

八、拓扑排序
例 :学生选修课排课先后顺序
拓扑排序:把有向图中各顶点按照它们相互之间的优先关系排列成一个线性序列的过程。
方法:
在有向图中选一个没有前驱的顶点且输出
从图中删除该顶点和所有以它为尾的弧
重复上述两步,直至全部顶点均已输出(拓扑排序成功),或者当图中不存在无前驱的顶点(图中有回路)为止。
---------------------------------------Code--------------------------------------
void TopologicalSort()/*输出拓扑排序函数。若G无回路,则输出G的顶点的一个拓扑序列并返回OK,否则返回ERROR*/
{
int indegree[M];
int i,k,j;
char n;
int count=0;
Stack thestack;
FindInDegree(G,indegree);//对各顶点求入度indegree[0....num]
InitStack(thestack);//初始化栈
for(i=0;i<G.num;i++)
Console.WriteLine("结点"+G.vertices[i].data+"的入度为"+indegree[i]);
for(i=0;i<G.num;i++)
{
if(indegree[i]==0)
Push(thestack.vertices[i]);
}
Console.Write("拓扑排序输出顺序为:");
while(thestack.Peek()!=null)
{
Pop(thestack.Peek());
j=locatevex(G,n);
if (j==-2)
{
Console.WriteLine("发生错误,程序结束。");
exit();
}
Console.Write(G.vertices[j].data);
count++;
for(p=G.vertices[j].firstarc;p!=NULL;p=p.nextarc)
{
k=p.adjvex;
if (!(--indegree[k]))
Push(G.vertices[k]);
}
}
if (count<G.num)
Cosole.WriteLine("该图有环,出现错误,无法排序。");
else
Console.WriteLine("排序成功。");
}
----------------------------------------Code--------------------------------------
算法的时间复杂度O(n+e)。

㈥ Java的排序算法有哪些

java的排序大的分类可以分为两种:内排序和外排序。在排序过程中,全部记录存放在内存,则称为内排序,如果排序过程中需要使用外存,则称为外排序。下面讲的排序都是属于内排序。
1.插入排序:直接插入排序、二分法插入排序、希尔排序。
2.选择排序:简单选择排序、堆排序。
3.交换排序:冒泡排序、快速排序。
4.归并排序
5.基数排序

㈦ java有哪些垃圾回收算法

常用的垃圾回收算法有:
(1).引用计数算法:
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器都为0的对象就是不再被使用的,垃圾收集器将回收该对象使用的内存。
引用计数算法实现简单,效率很高,微软的COM技术、ActionScript、Python等都使用了引用计数算法进行内存管理,但是引用计数算法对于对象之间相互循环引用问题难以解决,因此java并没有使用引用计数算法。
(2).根搜索算法:
通过一系列的名为“GC Root”的对象作为起点,从这些节点向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Root没有任何引用链相连时,则该对象不可达,该对象是不可使用的,垃圾收集器将回收其所占的内存。
主流的商用程序语言C#、java和Lisp都使用根搜素算法进行内存管理。
在java语言中,可作为GC Root的对象包括以下几种对象:
a. java虚拟机栈(栈帧中的本地变量表)中的引用的对象。
b.方法区中的类静态属性引用的对象。
c.方法区中的常量引用的对象。
d.本地方法栈中JNI本地方法的引用对象。
java方法区在Sun HotSpot虚拟机中被称为永久代,很多人认为该部分的内存是不用回收的,java虚拟机规范也没有对该部分内存的垃圾收集做规定,但是方法区中的废弃常量和无用的类还是需要回收以保证永久代不会发生内存溢出。
判断废弃常量的方法:如果常量池中的某个常量没有被任何引用所引用,则该常量是废弃常量。
判断无用的类:
(1).该类的所有实例都已经被回收,即java堆中不存在该类的实例对象。
(2).加载该类的类加载器已经被回收。
(3).该类所对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射机制访问该类的方法。
Java中常用的垃圾收集算法:
(1).标记-清除算法:
最基础的垃圾收集算法,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成之后统一回收掉所有被标记的对象。
标记-清除算法的缺点有两个:首先,效率问题,标记和清除效率都不高。其次,标记清除之后会产生大量的不连续的内存碎片,空间碎片太多会导致当程序需要为较大对象分配内存时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
(2).复制算法:
将可用内存按容量分成大小相等的两块,每次只使用其中一块,当这块内存使用完了,就将还存活的对象复制到另一块内存上去,然后把使用过的内存空间一次清理掉。这样使得每次都是对其中一块内存进行回收,内存分配时不用考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
复制算法的缺点显而易见,可使用的内存降为原来一半。
(3).标记-整理算法:
标记-整理算法在标记-清除算法基础上做了改进,标记阶段是相同的标记出所有需要回收的对象,在标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理。
标记-整理算法相比标记-清除算法的优点是内存被整理以后不会产生大量不连续内存碎片问题。
复制算法在对象存活率高的情况下就要执行较多的复制操作,效率将会变低,而在对象存活率高的情况下使用标记-整理算法效率会大大提高。
(4).分代收集算法:
根据内存中对象的存活周期不同,将内存划分为几块,java的虚拟机中一般把内存划分为新生代和年老代,当新创建对象时一般在新生代中分配内存空间,当新生代垃圾收集器回收几次之后仍然存活的对象会被移动到年老代内存中,当大对象在新生代中无法找到足够的连续内存时也直接在年老代中创建。

㈧ 求JAVA.数据结构.算法学习视频百度云。

《数据结构课程精讲教案合集-复旦大学(共计1061页).pdf 》网络网盘免费资源下载

链接: https://pan..com/s/15uwjtHgKKzZdheWFQC21pQ

?pwd=abzc 提取码: abzc

热点内容
wow刷碎片脚本 发布:2024-11-29 15:58:24 浏览:589
明小子源码 发布:2024-11-29 15:15:30 浏览:143
苹果8plus什么配置 发布:2024-11-29 14:16:36 浏览:677
androidmvp结构 发布:2024-11-29 14:16:34 浏览:535
androidsqlite命令 发布:2024-11-29 14:04:38 浏览:156
信用卡分期算法 发布:2024-11-29 13:50:56 浏览:807
安卓手机dll文件为什么打不开 发布:2024-11-29 13:40:49 浏览:1001
百分之五十石碳酸怎么配置 发布:2024-11-29 13:38:56 浏览:972
我的世界服务器如何装资源包 发布:2024-11-29 13:25:48 浏览:21
mc服务器的ip是什么 发布:2024-11-29 13:23:33 浏览:568