Java面试题-阿里、饿了么、链家、携程

版权声明:本文为hoaven原创文章,未经博主允许不得转载。 https://blog.csdn.net/hehuanchun0311/article/details/81123148

一、J2SE部分

1、Java基础


==与equals()方法的区别?

1. ==在比较基本数据类型时比较的是值,比较两个对象时比较的是地址值;
2. equals()方法存在于Object类中,Object类中equals()方法底层依赖的是==操作,
   在所有没有重写equals()的类中,调用equals()其实和使用==的效果一样,也是比较的地址值。
   String重写了equals(),底层比较的是两个String对应位置的char字符是否==。

为什么重写equals()方法就必须重写hashCode()方法?

1. Object.hashCode()方法是一个本地native方法,返回的是对象引用中存储的对象的内存地址;
2. 基于散列的集合(HashSet、HashMap和Hashtable)存放key时,调用该对象(存入对象)的hashCode()方法来得到该对象的hashCode值,然后根据该hashCode值决定该对象在HashSet中存储的位置;
3. 所以如果equals方法返回true,那么两个对象的hasCode()返回值必须一样;

Object有哪些方法?

1. equals()、hashCode()、getClass()、toString()--默认字符串:类名+哈希编码;
2. clone():实现对象的浅复制(当改变其中一个对象的引用类型属性实例的属性时,另一个对象相应的引用类型的属性实例中的属性也会发生变化),只有实现了Cloneable接口才可以调用该方法。否则抛出CloneNotSupportedException;深复制:引用类型属性也要实现clone()方法并显式调用;
3. finalize():用于JVM对象收集;
4. wait():使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断;
5. notify():唤醒在该对象上等待的某个线程;
6. notifyAll():唤醒在该对象上等待的所有线程;

接口和抽象类的区别,什么情况下用接口或抽象类?

区别:接口里面方法都是没有实现的,抽象类里面可以有部分是实现的方法。
抽象类:只有一个或几个方法需要定义成抽象方法,从而让子类来实现;其他方法具有清晰、明确、共同的逻辑需要在本类中实现,由子类“共享”和“复用”。
接口:纯粹定义方法接口,所有方法都由实现该接口的类来实现。没有共享的公共逻辑,此时用接口比较合适。

String、StringBuffer、StringBuilder区别

不可变:String,底层是final char value[]
可变:StringBuffer、StringBuilder,底层是char[] value
线程安全:StringBuffer
线程不安全:StringBuilder

为什么String设计成不可变?

1. 字符串常量池的需要:字符串常量池是Java堆内存中一个特殊的存储区域, 当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象;
2. 允许String对象缓存HashCode:Java中String对象的哈希码被频繁地使用,字符串不变性保证了hash码的唯一性,因此可以放心地进行缓存。这也是一种性能优化手段,意味着不必每次都去计算新的哈希码;
3. 安全性:String常被用来当做参数,例如 网络连接地址URL,文件路径path,还有反射机制所需要的String参数等, 假若String不是固定不变的,将会引起各种安全隐患。

序列化和反序列化?

概念:
1. 把对象转换为字节序列的过程称为对象的序列化;
2. 把字节序列恢复为对象的过程称为对象的反序列化。
用途:
1. 把对象的字节序列永久地保存到硬盘上,让它们离开内存空间,入住物理硬盘,等要用了,再把保存在硬盘中的对象还原到内存中;
2. 在网络上传送对象的字节序列;
实现:
1. ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中;
2. ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回;
3. 只有实现了Serializable接口的类的对象才能被序列化;
serialVersionUID的作用:
1. 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;
2. 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。

SpringBoot优缺点?

优点:快速构建项目、对主流开发框架的无配置集成(约定优于配置)、项目可独立运行,无须外部依赖Servlet容器(内置tomcat插件)、Spring Maven版本统一管理(避免版本冲突);
缺点:集成度较高,使用过程中不太容易了解底层,部署出现问题难以排查;

为什么要用注册中心?

1. 服务注册后,及时发现;
2. 服务宕机后,及时下线;
3. 服务发现时,进行路由;

2、集合


list和set的区别,linklist和arraylist的区别和应用场景;

list:有序、可重复、可插入null
set:无序、不可重复、只能插入一个null值
linklist:链表实现,方便插入和删除
arraylist:数组实现,方便随机访问

Arraylist、HashMap的初始容量、加载因子、扩容增量?

ArrayList::10、1、原容量的0.5倍
HashMap:16、0.75、原容量的1倍

有序的Map有哪些?为什么TreeMap是有序的?哪些集合是线程安全的?

有序Map: LinkedHashMap(按插入顺序)、TreeMap(默认是按key的升序排序,也可以重写Comparator方法自定义排序)
TreeMap有序原因:实现了SortedMap接口
线程安全的集合:Vector、Hashtable、ConcurrentHashMap

HashMap的底层数据结构,是如何插入的?哈希冲突解决方案?

底层:HashMap底层实现还是数组,只是数组的一个元素可能是一个单链表(哈希冲突时才是链表)。
插入:put过程是先计算hash然后通过hash与table.length取摸计算index值,然后将key放到table[index]位置,当table[index]已存在其它元素时(哈希冲突),会在table[index]位置形成一个链表,将新添加的元素放在table[index],原来的元素通过Entry的next进行链接(新值链头,原值后移)--哈希冲突的解决方案;
获取:先根据key的hash值得到这个元素在数组中的位置,然后通过key的equals()在链表中找到key对应的Entry节点;

HashMap和Hashtable的区别?

区别:
a. 主要的区别在于Hashtable是线程安全,而HashMap则非线程安全;Hashtable的实现方法里面都添加了synchronized关键字来确保线程同步,若无特殊需求建议使用HashMap,在多线程环境下若使用HashMap需要使用Collections.synchronizedMap()方法来获取一个线程安全的集合;
注:HashSet内部使用HashMap,把存储的值作为 key。

ConcurrentHashMap实现?JDK1.8之后优化?

要实现线程安全,就需要加锁, HashTable就是线程安全的, 但是HashTable对整张表加锁的做法非常消耗性能, ConcurrentMap的做法简单来说, 就是把哈希表分成若干段, 对其中某一段操作时, 只锁住这一段, 其他段可以不受影响。
实现:
a. 整个ConcurrentMap由一个segment数组组成(即segments),数组中每一个segment是一张哈希表, 哈希表中存放的是一张hashentry链表。Segment继承ReentrantLock用来充当锁的角色。
b. 最终存储key,value时是对segment操作, 因此只要对需要插入键值对的segment上锁就可以保证线程安全。
JDK1.8之后的优化:
1. 取消segment字段,直接采用transient volatile HashEntry<K,V> table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率;transient修饰的变量其内容在序列化后无法获得访问;
2. 将原先table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构;

3、线程


进程和线程的区别?

(1)进程是资源的分配和调度的一个独立单元,而线程是CPU调度的基本单元;
(2)同一个进程中可以包括多个线程,并且线程共享整个进程的资源;
(3)线程中执行时一般都要进行同步和互斥,因为他们共享同一进程的所有资源;

线程安全与非线程安全?

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染;
线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据;

线程的生命周期

新建(new Thread):当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。t = new Thread();
就绪(runnable):线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源。t.start();
运行(running):线程获得CPU资源正在执行任务(run()方法);
死亡(dead):当线程执行完毕、发生异常或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。
堵塞(blocked):由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。

多线程的实现方式,start()是立刻启动吗?

实现:继承Thread类、实现Runnable接口、线程池(Cache、Fixed、Single、Schedule)、实现Callable接口通过FutureTask包装器来创建Thread线程;
不是立刻启动:执行start()之后,线程等待CPU调度,当调度成功时,调用run()才启动线程;

volatile和synchronized的区别?

>volatile关键字的作用是:使变量在多个线程间可见(可见性)
a. 强制变量每次从内存中读取,而不会存储在寄存器中(强制刷新到内存中);synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住;
b. volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的;
c. volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞;

Lock和synchronized区别?

a. Lock是一个接口,而synchronized是java的一个关键字
b. synchronized在发生异常时候会自动释放占有的锁,因此不会出现死锁;而lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生。synchronized同步的代码块执行结束后锁可以由JVM自动释放,Lock不行。
c. Lock可以通过trylock来知道有没有获取锁,而synchronized不能;
d. Lock可实现读写分离,ReentrantReadWriteLock;

wait()和notify()

a. wait()使线程停止运行;
b. notify()使停止的线程继续运行;
c. wait(long)等待long时间,如果没有唤醒,自动苏醒;

sleep() 和 wait() 区别?

wait()是Object类的方法,sleep()和yield()是Thread类的静态方法。sleep和yield方法是用来让线程让出CPU时间,把CPU交给线程调度器,使得其他线程能获得CPU时间。sleep()睡眠时,保持对象锁;而wait()睡眠时,释放对象锁。

什么是阻塞?阻塞的情况?

阻塞状态是指线程因为某种原因放弃了cpu使用权,也即让出了cpu时间片,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu时间片转到运行(running)状态。
a. 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
b. 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
c. 其他阻塞:运行(running)的线程执行Thread.sleep(long ms),JVM会把该线程置为阻塞状态。当sleep()状态超时,线程重新转入可运行(runnable)状态。)

Semaphore和LockSupport的作用?

Semaphore:计数信号量。Semaphore管理一系列许可证。它的每个acquire方法阻塞,直到有一个许可证可以获得然后拿走一个许可证;每个release方法增加一个许可证。经常用于限制获取某种资源的线程数量。
LockSupport:它的park()和unPark()对应Object的wait()和notify()。

CAS

Compare And Set的简称。现代主流CPU都支持的一种硬件级别的原子操作, 比较并交换, 操作包含三个操作数:内存地址(V) 预期原值(A) 新值(B)
如果内存位置的值与预期原值相匹配, 那么处理器会自动将该位置值更新为新值,否则, 处理器不做任何操作.无论哪种情况, 它都会在CAS指令之前返回该位置的值.

ReentrantLock的实现

基于AQS:volatile int state;
获取锁:尝试对state进行CAS(0,1)操作,尝试几次失败后就挂起线程,进入等待队列。
释放锁:对state进行CAS(1,0)操作,然后去唤醒等待队列中的一个线程。
//默认创建非公平锁
public ReentrantLock() {
    sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

ThreadPoolExecutor的重要参数?执行顺序?

corePoolSize:核心线程数
1. 核心线程会一直存活,及时没有任务需要执行;
2. 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理;
3. 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭;
queueCapacity:任务队列容量(阻塞队列)
1. 当核心线程数达到最大时,新任务会放在队列中排队等待执行
maxPoolSize:最大线程数
keepAliveTime:线程空闲时间
1. 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize;
2. 如果allowCoreThreadTimeout=true,则会直到线程数量=0;
allowCoreThreadTimeout:允许核心线程超时
ThreadPoolExecutor执行顺序:
1. 当线程数小于核心线程数时,创建线程。
2. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
3. 当线程数大于等于核心线程数,且任务队列已满
	 若线程数小于最大线程数,创建线程;
	 若线程数等于最大线程数,抛出异常,拒绝任务;

4、JVM


JVM参数

堆大小:-Xms3550m, 初始化堆大小;-Xmx3550m,最大堆大小。堆 = 年轻代 + 年老代+ 持久代
永久代:-XX:Permsize=512m,设置永久代初始值;-XX:MaxPermsize=512m,设置永久代的最大值。 
收集器:-XX:MaxGCPauseMillis=10,设置垃圾收集停顿的最大毫秒数;

OOM解决方案

OOM for Perm:增加永久保存区域(这块内存主要是被JVM存放Class和Meta信息的)大小:-XX:MaxPermSize(默认64M)
OOM for GC:对象过多,导致内存溢出-->调整GC的策略,比如老生代存活率设置为80%;

垃圾收集算法

a. 标记-清除:标记需要回收的对象;清除被标记的对象;
b. 复制:内存分为两块,每次只用一块;当这一块使用完后,将还存活的对象放到另一块中去;未复制对象清除;
c. 标记-整理:标记需要回收的对象;让所有可用的对象都向一端移动;然后直接清理掉端边界的对象内存;
d. 分代收集:新生代(新生成的对象,采用复制算法)+ 老年代(经历了N次垃圾回收后仍然存活的对象,采用标记-清除或标记-整理算法);

标记:

引用计数:引用一次+1,引用失败-1,引用计数器为0即被标记;

可达性分析:通过一系列称为"GC Root"的对象作为起始点,向下搜索行程引用链,不在引用链中即为被标记;

5、数据库


数据库查询缓慢是什么原因,如何优化?

原因:执行复杂的逻辑或统计、全表扫描、索引失效、join的表过多等;
索引失效优化:
a. where 子句中使用!=或<>操作符、函数操作、表达式(如:num/2)以及null值判断;
b. in 、 not in 、or 也要慎用,否则条件超过一定数量会导致索引失效;
c. 模糊查询(LIKE)时避免在关键词前使用”%”(如:LIKE '%小分期');
其他优化:
a. JOIN时使用小结果集驱动大结果集;
b. JOIN时条件判断应放在on中,避免在后面加where;

数据库事务的用途,如何开启?

try {//设置禁止自动提交事务
    connection.setAutoCommit( false);
    通过conn得到stmt进行数据库操作...
    connection.commit(); //统一提交
} catch(Exception ex) {
    connection.rollback();
} finally {
    connection和statement的关闭
}

数据库的乐观锁和悲观锁?

乐观锁:乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。
实现:乐观锁不是数据库自带的,需要我们自己去实现。比如更新记录时检查当前记录version字段值是否与数据库中记录当前version一致。
悲观锁:悲观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作。
实现:悲观锁是由数据库自己实现了的,使用Repeatable Read的隔离级别,然后执行相应的SQL语句即可。

MySQL的共享锁和排他锁?

共享锁:共享锁又称读锁,是读取操作创建的锁。如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。
使用:SELECT ... LOCK IN SHARE MODE;
排他锁:排他锁又称写锁,如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。
使用:SELECT ... FOR UPDATE;

数据库事务的特性(ACID)和隔离级别?

ACID:
1)原子性: 原子性是指事务是一个不可再分割的工作单位,事务中的操作要么都发生,要么都不发生。
2)一致性:数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。
3)隔离性:隔离性是指并发的事务是相互隔离的。
4)持久性:持久性是指在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。即使出现了任何事故比如断电等,事务一旦提交,则持久化保存在数据库中。
隔离级别:
a. Read uncommitted:读未提交,一个事务可以读取另一个未提交事务的数据。引发脏读;
b. Read committed:读提交,一个事务要等另一个事务提交后才能读取数据。引发不可重复读(UPDATE操作:A准备用卡买单时,B正使用该卡全额付款并提交后,A再买单时发现余额不足);
c. Repeatable read:重复读,就是在开始读取数据(事务开启)时,不再允许修改操作。引发幻读(INSERT操作:程序员某一天去消费,花了2千元,然后他的妻子去查看他今天的消费记录(全表扫描FTS,妻子事务开启),看到确实是花了2千元,就在这个时候,程序员花了1万买了一部电脑,即新增INSERT了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交),发现花了1.2万元,似乎出现了幻觉,这就是幻读);
d. Serializable:事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用;
// Oracel、Sql Server默认Read committed,Mysql默认Repeatable read;

MySQL分布式事务?

InnoDB存储引擎提供了对XA事务的支持,并通过XA事务来支持分布式事务的实现。分布式事务指的是允许多个独立的事务资源参与到一个全局的事务中。事务资源通常是关系型数据库系统,但也可以是其他类型的资源。在使用分布式事务时,InnoDB存储引擎的事务隔离级别必须设置为Serializable。

什么是死锁,死锁的四个必要条件?

死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象(双方都掌握有对方所要的资源);
必要条件:
a. 互斥条件:资源是独占的且排他使用;
b. 不可剥夺条件:进程所获得的资源在未使用完毕之前,不被其他进程强行剥夺;
c. 请求和保持条件:进程每次申请它所需要的一部分资源,在申请新的资源的同时,继续占用已分配到的资源;
d. 循环等待条件:在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路;

如何处理死锁?

预防死锁:

a. 资源一次性分配:(破坏请求和保持条件)
b. 可剥夺资源:即当某进程新的资源未满足时,释放已占有的资源(破坏不可剥夺条件)
c. 资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)

避免死锁:

预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得较满意的系统性能。由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法。

缓存穿透和缓存雪崩?

缓存穿透含义:一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就去DB查找。如果key对应的value是一定不存在的,并且对该key并发请求量很大,就会对DB造成很大的压力。这就叫做缓存穿透。
缓存穿透解决方案:
1. 对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存;
2. 对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的set中,查询时通过该set过滤(用的较少);
缓存雪崩含义:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给DB带来很大压力;
缓存雪崩解决方案:
1. 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待;
2. 不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀;
3. 做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期;

二、计算机基础部分

TCP的三次握手和四次挥手,浏览器访问一个网址的过程?

位码即tcp标志位,有6种表示:SYN(synchronous建立连接)、ACK(acknowledgement 表示响应、确认)、PSH(push表示有DATA数据传输)、FIN(finish关闭连接)、RST(reset表示连接重置)、URG(urgent紧急指针字段值有效)
三次握手(建立连接):
第一次握手:客户端发送syn包(syn=x)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
四次挥手(断开连接):
第一次挥手:主动关闭方发送一个FIN,用来关闭主动方到被动关闭方的数据传送。也就是主动关闭方告诉被动关闭方:我已经不会再给你发数据了。
第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号)。
第三次挥手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。
第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手。
访问过程:域名解析-->三次握手-->传输数据-->四次挥手

TCP与UDP区别?

1. TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接;
2. TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付;
3. 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信;
4. UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信;

linux中文件权限

rwxr-xr-x(或755):包括属主、属组、其他
chomd 文件名 权限

HTTP、HTTPS区别?

a. HTTP协议传输的数据都是未加密的,也就是明文的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输
b. https协议需要到ca申请证书;

三、数据结构部分

常用排序思想

插入排序

每步将一个待排序的记录,按其顺序码大小插入到前面已经排序的字序列的合适位置,直到全部插入排序完为止。

直接插入排序:每步将一个待排序的记录,按其顺序码大小插入到前面已经排序的字序列的合适位置(从后向前找到合适位置后),直到全部插入排序完为止。
二分法插入排序:二分法插入排序的思想和直接插入一样,只是找合适的插入位置的方式不同,这里是按二分法找到合适的位置,可以减少比较的次数。
希尔排序:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2。

选择排序

每趟从待排序的记录序列中选择关键字最小的记录放置到已排序表的最前位置,直到全部排完。

直接选择排序:在要排序的一组数中,选出最小的一个数与第一个位置的数交换;然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。
堆排序:

交换排序

冒泡排序:在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。
快速排序:选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。

归并排序

归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

猜你喜欢

转载自blog.csdn.net/hehuanchun0311/article/details/81123148
今日推荐