关于Hadoop的杂乱无章(续更)
JPS(是jdk的工具):表示查看当前主机有哪些运行的进程
NameNode :表示主节点
DataNode:表示数据节点
SecondaryNameNode :表示次要名称节点
--节点表示:一台机器
进程是运行在机器上的,一个软件可以有多个进程(分布式软件:Hadoop)
HDFS只是Hadoop的一部分,Hadoop还有MR、yarn
HDFS是分布式软件系统:将文件自动分布在三台机器上(副本/备份)
HDFS的特点:
1.可容错:表示你把软件删除了,还可以复原
2,低廉硬件(安装了x86服务器/CPU)
3.高吞吐量(IO):分布多台机器同时处理
4.适用于有超大数据集的应用程序(1G以上大小的表格)
--但是不能满足:update(随机读写),但是可以以流的形式访问文件,提高了性能
---------
POSIX:可移植操作系统接口(是一个规范)
说白就是一个文件系统的接口,ext4、ext3、FAT32、NTFS全部都实现了这个接口,但是功能不一定全部实现
-HDFS框架是有中心的架构
主节点是:NameNode
用于对外(client)通信,通过交换机接收和发送,但是查询到的数据不经过这里直接由DataNode交给client
管理+协调,可以控制其他节点
完成任务(干活节点):DataNode
DataNode之间没有关联,之间的通信也是通过局域网
--实际生产环境中是有多个NameNode
MateData(元数据):元数据就是形容数据的数据
在这里他包含NameSpace:目录结构+blockdata(块数据)
HDFS是分块存储的,块表示一个文件存储在哪台机器上
------------
client(客户端)与NameNode之间是元数据交换
client与DataNode之间是数据交换
在同一个机架上通信快,HDFS会在同一个机架上放两个数据(在两台机器上),在不同的机架上保存一个(备份)
128M是一个block(块)
block就是键值对--映射到内存中就是元数据
key:block的id(哪台机器)-----------value:block的内容地址
--文件越小消耗的内存越大,因为保存相同大小,文件越多分的块越多,映射到内存中的元数据越多
例如:文件1024G,保存1T大小的这样的文件,占用内存8M,磁盘大小是3T
文件1M,保存1pb大小的这样的文件,占用内存1T,磁盘大小是3pb
分块是客户端
客户端将文件分块,串行依次写入,分的块不一定保存在同一台机器上
1.向文件系统(Hadoop集群)中上传文件
hadoop fs -put /abc /
/abc:表示将要上传的文件
/:表示上传到集群中的路径是:/
----------
Fs文件系统----保存file
数据库(DB)----保存表格/table-----存入之后需要经常修改
HDFS---file----crd(没有u不能随机读写,但是能追加),
记住HDFS与数据库无关,HDFS是处理海量数据的,不能经常修改
---
OLTP:在线事务处理(对数据库的写操作)
主要是数据库操作--web网站
特点:实时(立即有效果),处理的数据量小
OLAP:对数据库的读、分析处理
主要是:Hadoop
特点:实时不高,数据量大,HDFS是一次写入多次读取的模式
--元数据:ns(目录结构)+blockMap(文件所在的地址)
Secondary不是NameNode的备份,因为NameNode与Secondary共存亡
他类似于秘书:将内存中的内容持久化
工作:将最近的一个image(存量)与edit—log(增量)合并--生成出最新的image,并将开始的image删除,循环操作
这是因为每写一个文件,都会产生一个edit-log
过程:NameNode将日志+最新的image发送给--Secondary,Secondary将这两个和合并,并删除原来的image,发送给NameNode一个最新的image(这两个是进程之间通信通过Http协议)
--Hadoop中的配置文件
permission权限,false:表示任何人都可以访问,实际生产环境中true
每次格式化,version中的NameSpaceID、clusterID(集群的id)改变了
集群在开启前几分钟会开启安全模式(SafeMode),DataNode向NameNode汇报数据信息,之后自动关闭
50010是集群之间的通信端口号,如果是非知名端口号,防火墙会拦截
hadoop fs -checksum /fileName:校验码,可以查看文件是否被改变(是否成为脏数据)
------mapreduce
hdfs:是分布式存储,可以单独使用
mapreduce是分布式计算,必须按照分布式存储才能分布式计算
map:映射、转化、分
reduce:合并、减少
MR是计算模型:可以并行化处理大量数据(提高效率)
a.并行(事):一件事分成多个快,多个人同时做
b.并发(人):多个人同时做一件事
cdh与hdp是两家最大的hadoop上市公司(在2018年听说要合并)
以后spark(函数式设计语言)会代替mapreduce,因为mapreduce相对于比较慢、难以维护。
-------实际生产中的集群需求:
MapReduce集群搭建
1.yarn环境
在/usr/local/hadoop/etc/hadoop/yarn-env.sh中配置--java安装路径
2.MapReduce配置 IP:8088
将mapred-site.xml.tmplate复制成mapred-site.xml
cp mapred-site.xml.tmplate mapred-site.xml
将mapred-site.xml中添加配置
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
<property>
<name>mapreduce.jobhistory.address</name>
<value>master:10020</value>
</property>
<property>
<name>mapreduce.jobhistory.webapp.address</name>
<value>master:19888</value>
</property>
3.yarn配置
在yarn-site.xml中添加配置
<property>
<name>yarn.resourcemanager.hostname</name>
<value>master</value>
这里表示yarn存放在master,这里value中的值填什么yarn就在那里
</property>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
--如果不是单机伪分布集群则处理一下操作
4.将etc/hadoop复制到其他节点
5.最终启动
启动
执行start-yarn.sh命令(在这之前确保HDFS已经启动,没有启动的先start-dfs.sh)。
执行成功后,通过JPS检查ResourceManager、NodeManager是否启动。
如果启动成功,通过master:8088可以打开MapReduce“应用”站点:
注意:需要配置本地Windows系统的hosts文件才能在本地使用主机名!
phoenix在测试javaAPI的注意:
(1)插入值如果是字符串要用单引号引起来,切记不能用双引号!!!
(2)表名如果要体现小写效果,必须要用双引号!!!
upsert into "person" values (1, 'test', 100); 正确
upsert into "person" values (1, "test", 100); 错误
HBase的协处理器
过程:
Java写协处理器代码-------打jar包-------上传到hbase/lib目录下-----重启hbase(为了加载jar包)-------在hbase上创建表----为表添加自定义的协处理器(alter ‘tableName’,’coprocessor’ => ‘|classPath|‘)------测试:put数据。
类:将索引放在另一张表中,表也不放在hbase上,放在sole上。
- EndPoint:数据存储过程(hbase的协处理器—现在已经不常用)
存储在mysql中的多条sql语句的集合,执行多条语句,提高性能
但是最致命的缺点是:迁移性差(写的越多,迁移的时候要写的sql语句越多)
b. Observer(触发器):就是在写入数据之前,为表创建二级索引
DML:观察表数据的变化—CURD(增删改查)-----在Observer中RegionObserver
DDL:观察表的元数据变化---创表删表之类-------在Observer中MasterObserver
WAL:写之前记录日志
索引:排序之后的文件---有索引,先读取索引文件,再查询数据内容。(不用全表扫描)
覆盖索引(非主键的索引):增大索引文件,但是读取数据快,也会稍微降低写入数据的速度(以空间换时间)
Hbase本身只有rowKey一个索引。
可以利用phoenix插件可以随意创建二级索引,提高hbase的查询速度;
也可以在写入数据之前对表添加协处理器。
Hbase RowKey设计:
- rowKey的大小:不是很大
长度不宜过长:<=256字节
- rowKey与业务整合:
因为hbase只有rowKey一个索引,创建的时候尽量与业务逻辑整合(就是查询的条件放在rowKey中);为了命中索引,提高查询速度。
- rowKey散列:为了避免写热点
写热点:数据倾斜(数据分布不均匀)
- 加随机前缀(不建议使用)--因为需要另保存前缀用来查询数据
- 加hash前缀(建议使用)--由hash算法就可以取前缀值,用来查询
- 倒叙写入(偷懒式)
比如:1-2018-start--------------àtrats-8102-1
先构造rowKey,再倒序。
正序可能(数据)递增,而导致写热点。
----------libreoffice
下载:
Linux centos
https://donate.libreoffice.org/zh-CN/dl/rpm-x86_64/6.0.6/zh-CN/LibreOffice_6.0.6_Linux_x86-64_rpm.tar.gz
安装:
1.解压文件
3.yum localinstall *.rpm
文档转换:
word→pdf
libreoffice6.0 --invisible --convert-to pdf some.doc
word→html
libreoffice6.0 --invisible --convert-to html some.doc
或者
soffice --convert-to pdf somedoc
转换(word-->pdf)出现下面错误,请下载插件
yum -y install ibus
[root@master209 local]# libreoffice --invisible --convert-to pdf 1.doc
-bash: libreoffice: command not found
[root@master209 local]# libreoffice6.0 --invisible --convert-to pdf 1.doc
/opt/libreoffice6.0/program/soffice.bin: error while loading shared libraries: libcairo.so.2: cannot open shared object file: No such file or directory
异常处理:
word->pdf时中文乱码
1.打开c盘下的Windows/Fonts目录
2.在这之前我们还需要新建目录,首先在/usr/shared/fonts目录下新建一个目录chinese:
3.然后就是将上面的两个字体上传至/usr/share/fonts/chinese目录下即可
chmod -R 755 /usr/share/fonts/chinese
4.刷新内存中的字体缓存,这样就不用reboot重启了,输入:fc-cache
5.最后再次通过fc-list看一下字体列表:(ps可能我添加的比较多)
卸载libreoffice
yum erase libreoffice\*
--------------kafka
Kafka 消息队列、消息系统、消息中间件、消息的推送口
生产者-----à(中介)ß-------消费者
中间通过TCP通信
中间件的演化:
类---à软件
线程-à进程
Flum中agent表示一个节点(进程)
Kafka中broker表示一个节点(进程)
--特性:分布、可分区、可复制(备份)、顺序读写-速度高
Offset:消费者的(每个分区的)偏移量
Offset是有消费者来维护
Topic:话题—默认是hash分区
每个topic的领导者是相对的
一条数据(一个分区)可以由不同组的多个消费者来消费
有两种模式:排队、订阅
消息队列的种类:
1.ActiceMQ java
2.zero MQ
3.Rabbit MQ
4. Rocket MQ 阿里云开元
5.kafka
以上都是消息队列,都可以相互替换,一般2,3不常用—使用消息队列就是为了解耦
一个分区,一个组 能保证有序性
在0.11之前kafka将元数据保存在zookeeper中
以日志的形式将数据持久化
分区个数 按照kafka集群个数的10倍关系
Kafka中的相关配置
- acks
设置为all:安全,可能数据重复,先复制,再告诉
à-1 提高性能,但是不安全
à1 数据在leader接收之后,立即回复,如果中介机器宕机,可能丢失
16kb----批量发送的单位
Batch(缓冲区-一次发送的数据)与linger(间隔)满足之一、都会发送
Kafka---à发送的是json字符串
Enable.auto.commit--àtrue 自动提交
消费者取出之后就告诉别人消费过了,消费者还没有消费就告诉,如果中间shutdown了,数据就丢失。
防止数据重复与数据丢失---偏移量问题
幂等(事务控制)+手动
Auto.offset.reset---àearliest(最早) 有--(上次) 无---(从头)
-àbest (最近) 数据丢失
-ànone 测试用
---------------------spark
spark.streaming._ 实时处理数据
localhost:4040
storm与stream的区别:
storm:每次处理一条数据,快,但是处理的数据小(吞吐量小)
stream:每次可以快速处理一批数据,特征:high-throughput(高吞吐)、fault-tolerant(可容错)、scalable(可扩展)
配置信息:
streaming至少两个线程
new SparkConf().setMaster("local[*]").setAppName("streaming")
时间用于分割数据:Seconds(5)---5秒
new StreamingContext(conf,Seconds(5))
数据的来源是通过tcp通信获取Datastream
Datastream:表示一系列的rdd
对Dstream做map,就是对每个rdd做map
模拟从tcp端口读取数据
val ds=ssc.socketTextStream("localhost",999)
//启动streaming context,防止没有数据关闭
//如果没有接受导数据,也不会立刻关闭,会尝试一段时间强制关闭
ssc.start()
ssc.awaitTermination()
--------------------------mobaxterm配置
Settings
Configuration
Terminal
勾选 Paste using right-click(左键选取,松开左键复制,右键粘贴, 可以使用Ctrl+右键来使用右键功能)
SSH
取消勾选 Automatically switch to SSH-browser tab after login(登录后自动切换到SFTP浏览器)
勾选 SSH keepalive(保持心跳连接不断)
------
命令 hadoop dfsadmin -safemode get 查看安全模式状态
命令 hadoop dfsadmin -safemode enter 进入安全模式状态
命令 hadoop dfsadmin -safemode leave 离开安全模式
--------------spark中rdd、dataframe、dataset联系与区别
在spark中,RDD、DataFrame、Dataset是最常用的数据类型,本博文给出笔者在使用的过程中体会到的区别和各自的优势
共性:
1、RDD、DataFrame、Dataset全都是spark平台下的分布式弹性数据集,为处理超大型数据提供便利
2、三者都有惰性机制,在进行创建、转换,如map方法时,不会立即执行,只有在遇到Action如foreach时,三者才会开始遍历运算,极端情况下,如果代码里面有创建、转换,但是后面没有在Action中使用对应的结果,在执行时会被直接跳过,如
1 2 3 4 5 6 7 8 |
|
map中的println("运行")并不会运行
3、三者都会根据spark的内存情况自动缓存运算,这样即使数据量很大,也不用担心会内存溢出
4、三者都有partition的概念,如
1 2 3 4 5 6 7 8 |
|
这样对每一个分区进行操作时,就跟在操作数组一样,不但数据量比较小,而且可以方便的将map中的运算结果拿出来,如果直接用map,map中对外面的操作是无效的,如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
不使用partition时,对map之外的操作无法对map之外的变量造成影响
5、三者有许多共同的函数,如filter,排序等
6、在对DataFrame和Dataset进行操作许多操作都需要这个包进行支持
1 2 |
|
7、DataFrame和Dataset均可使用模式匹配获取各个字段的值和类型
DataFrame:
1 2 3 4 5 6 7 |
|
为了提高稳健性,最好后面有一个_通配操作,这里提供了DataFrame一个解析字段的方法
Dataset:
1 2 3 4 5 6 7 8 |
|
区别:
RDD:
1、RDD一般和spark mlib同时使用
2、RDD不支持sparksql操作
DataFrame:
1、与RDD和Dataset不同,DataFrame每一行的类型固定为Row,只有通过解析才能获取各个字段的值,如
1 2 3 4 5 |
|
每一列的值没法直接访问
2、DataFrame与Dataset一般与spark ml同时使用
3、DataFrame与Dataset均支持sparksql的操作,比如select,groupby之类,还能注册临时表/视窗,进行sql语句操作,如
1 2 |
|
4、DataFrame与Dataset支持一些特别方便的保存方式,比如保存成csv,可以带上表头,这样每一列的字段名一目了然
1 2 3 4 5 6 |
|
利用这样的保存方式,可以方便的获得字段名和列的对应,而且分隔符(delimiter)可以自由指定
Dataset:
这里主要对比Dataset和DataFrame,因为Dataset和DataFrame拥有完全相同的成员函数,区别只是每一行的数据类型不同
DataFrame也可以叫Dataset[Row],每一行的类型是Row,不解析,每一行究竟有哪些字段,各个字段又是什么类型都无从得知,只能用上面提到的getAS方法或者共性中的第七条提到的模式匹配拿出特定字段
而Dataset中,每一行是什么类型是不一定的,在自定义了case class之后可以很自由的获得每一行的信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
可以看出,Dataset在需要访问列中的某个字段时是非常方便的,然而,如果要写一些适配性很强的函数时,如果使用Dataset,行的类型又不确定,可能是各种case class,无法实现适配,这时候用DataFrame即Dataset[Row]就能比较好的解决问题
转化:
RDD、DataFrame、Dataset三者有许多共性,有各自适用的场景常常需要在三者之间转换
DataFrame/Dataset转RDD:
这个转换很简单
1 2 |
|
RDD转DataFrame:
1 2 3 4 |
|
一般用元组把一行的数据写在一起,然后在toDF中指定字段名
RDD转Dataset:
1 2 3 4 5 |
|
可以注意到,定义每一行的类型(case class)时,已经给出了字段名和类型,后面只要往case class里面添加值即可
Dataset转DataFrame:
这个也很简单,因为只是把case class封装成Row
1 2 |
|
DataFrame转Dataset:
1 2 3 |
|
这种方法就是在给出每一列的类型后,使用as方法,转成Dataset,这在数据类型是DataFrame又需要针对各个字段处理时极为方便
特别注意:
在使用一些特殊的操作时,一定要加上 import spark.implicits._ 不然toDF、toDS无法使用
------------hive中的列转行,与行转列
Hive
行转列和列转行
表1:cityInfo
表2:cityInfoSet
表1和表2的结构如上所示。如何在 hive 中使用 Hql 语句对表1和表2进行互相转化呢?
行转列
表1=>表2 可以使用 hive 的内置函数 concat_ws() 和 collect_set()进行转换:
执行代码如下所示:
select cityname,concat_ws(',',collect_set(regionname)) as address_set from cityInfo group by cityname;
1
列转行
表2=>表1 可以使用 hive 的内置函数 explode()进行转化。代码如下:
select cityname, region from cityInfoSet lateral view explode(split(address_set, ',')) aa as region;
------------------spark中的driver与executor
一、看了很多网上的图,大多是dirver和executor之间的图,都不涉及物理机器
如下图,本人觉得这些始终有些抽象
看到这样的图,我很想知道driver program在哪里啊,鬼知道?为此我自己研究了一下,网友大多都说是对的有不同想法的请评论
二、现在我有三台电脑 分别是
-
192.168.10.82 –>bigdata01.hzjs.co
-
192.168.10.83 –>bigdata02.hzjs.co
-
192.168.10.84 –>bigdata03.hzjs.co
集群的slaves文件配置如下:
-
bigdata01.hzjs.co
-
bigdata02.hzjs.co
-
bigdata03.hzjs.co
那么这三台机器都是worker节点,本集群是一个完全分布式的集群经过测试,我使用# ./start-all.sh ,那么你在哪台机器上执行的哪台机器就是7071 Master主节点进程的位置,我现在在192.168.10.84使用./start-all.sh
那么就会这样
三、那么我们来看看local模式下
现在假设我在192.168.10.84上执行了 bin]# spark-shell 那么就会在192.168.10.84产生一个SparkContext,此时84就是driver,其他woker节点(三台都是)就是产生executor的机器。如图
现在假设我在192.168.10.83上执行了 bin]# spark-shell 那么就会在192.168.10.83产生一个SparkContext,此时83就是driver,其他woker节点(三台都是)就是产生executor的机器。如图
总结:在local模式下 驱动程序driver就是执行了一个Spark Application的main函数和创建Spark Context的进程,它包含了这个application的全部代码。(在那台机器运行了应用的全部代码创建了sparkContext就是drive,也可以说是你提交代码运行的那台机器)
四、那么看看cluster模式下
现在假设我在192.168.10.83上执行了 bin]# spark-shell 192.168.10.84:7077 那么就会在192.168.10.84产生一个SparkContext,此时84就是driver,其他woker节点(三台都是)就是产生executor的机器。这里直接指定了主节点driver是哪台机器:如图
五、如果driver有多个,那么按照上面的规则,去判断具体在哪里
Driver: 使用Driver这一概念的分布式框架有很多,比如hive,Spark中的Driver即运行Application的main()函数,并且创建SparkContext,创建SparkContext的目的是为了准备Spark应用程序的运行环境,在Spark中由SparkContext负责与ClusterManager通讯,进行资源的申请,任务的分配和监控等。当Executor部分运行完毕后,Driver同时负责将SaprkContext关闭,通常SparkContext代表Driver.
上面红色框框都属于Driver,运行在Driver端,中间没有框住的部分属于Executor,运行的每个ExecutorBackend进程中。println(pcase.count())collect方法是Spark中Action操作,负责job的触发,因为这里有个sc.runJob()方法
def count(): Long = sc.runJob(this, Utils.getIteratorSize _).sum
hbaseRDD.map()属于Transformation操作。
总结:Spark Application的main方法(于SparkContext相关的代码)运行在Driver上,当用于计算的RDD触发Action动作之后,会提交Job,那么RDD就会向前追溯每一个transformation操作,直到初始的RDD开始,这之间的代码运行在Executor。
---------------------------top K
堆排解法
用堆排来解决Top K的思路很直接。
前面已经说过,堆排利用的大(小)顶堆所有子节点元素都比父节点小(大)的性质来实现的,这里故技重施:既然一个大顶堆的顶是最大的元素,那我们要找最小的K个元素,是不是可以先建立一个包含K个元素的堆,然后遍历集合,如果集合的元素比堆顶元素小(说明它目前应该在K个最小之列),那就用该元素来替换堆顶元素,同时维护该堆的性质,那在遍历结束的时候,堆中包含的K个元素是不是就是我们要找的最小的K个元素?
实现:
在堆排的基础上,稍作了修改,buildHeap和heapify函数都是一样的实现,不难理解。
速记口诀:最小的K个用最大堆,最大的K个用最小堆。
public class TopK {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] a = { 1, 17, 3, 4, 5, 6, 7, 16, 9, 10, 11, 12, 13, 14, 15, 8 };
int[] b = topK(a, 4);
for (int i = 0; i < b.length; i++) {
System.out.print(b[i] + ", ");
}
}
public static void heapify(int[] array, int index, int length) {
int left = index * 2 + 1;
int right = index * 2 + 2;
int largest = index;
if (left < length && array[left] > array[index]) {
largest = left;
}
if (right < length && array[right] > array[largest]) {
largest = right;
}
if (index != largest) {
swap(array, largest, index);
heapify(array, largest, length);
}
}
public static void swap(int[] array, int a, int b) {
int temp = array[a];
array[a] = array[b];
array[b] = temp;
}
public static void buildHeap(int[] array) {
int length = array.length;
for (int i = length / 2 - 1; i >= 0; i--) {
heapify(array, i, length);
}
}
public static void setTop(int[] array, int top) {
array[0] = top;
heapify(array, 0, array.length);
}
public static int[] topK(int[] array, int k) {
int[] top = new int[k];
for (int i = 0; i < k; i++) {
top[i] = array[i];
}
//先建堆,然后依次比较剩余元素与堆顶元素的大小,比堆顶小的, 说明它应该在堆中出现,则用它来替换掉堆顶元素,然后沉降。
buildHeap(top);
for (int j = k; j < array.length; j++) {
int temp = top[0];
if (array[j] < temp) {
setTop(top, array[j]);
}
}
return top;
}
}
时间复杂度
n*logK
速记:堆排的时间复杂度是n*logn,这里相当于只对前Top K个元素建堆排序,想法不一定对,但一定有助于记忆。
适用场景
实现的过程中,我们先用前K个数建立了一个堆,然后遍历数组来维护这个堆。这种做法带来了三个好处:(1)不会改变数据的输入顺序(按顺序读的);(2)不会占用太多的内存空间(事实上,一次只读入一个数,内存只要求能容纳前K个数即可);(3)由于(2),决定了它特别适合处理海量数据。
这三点,也决定了它最优的适用场景。
快排解法
用快排的思想来解Top K问题,必然要运用到”分治”。
与快排相比,两者唯一的不同是在对”分治”结果的使用上。我们知道,分治函数会返回一个position,在position左边的数都比第position个数小,在position右边的数都比第position大。我们不妨不断调用分治函数,直到它输出的position = K-1,此时position前面的K个数(0到K-1)就是要找的前K个数。
实现:
“分治”还是原来的那个分治,关键是getTopK的逻辑,务必要结合注释理解透彻,自动动手写写。
public class TopK {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] array = { 9, 3, 1, 10, 5, 7, 6, 2, 8, 0 };
getTopK(array, 4);
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + ", ");
}
}
// 分治
public static int partition(int[] array, int low, int high) {
if (array != null && low < high) {
int flag = array[low];
while (low < high) {
while (low < high && array[high] >= flag) {
high--;
}
array[low] = array[high];
while (low < high && array[low] <= flag) {
low++;
}
array[high] = array[low];
}
array[low] = flag;
return low;
}
return 0;
}
public static void getTopK(int[] array, int k) {
if (array != null && array.length > 0) {
int low = 0;
int high = array.length - 1;
int index = partition(array, low, high);
//不断调整分治的位置,直到position = k-1
while (index != k - 1) {
//大了,往前调整
if (index > k - 1) {
high = index - 1;
index = partition(array, low, high);
}
//小了,往后调整
if (index < k - 1) {
low = index + 1;
index = partition(array, low, high);
}
}
}
}
}
时间复杂度
n
速记:记住就行,基于partition函数的时间复杂度比较难证明,从来没考过。
适用场景
对照着堆排的解法来看,partition函数会不断地交换元素的位置,所以它肯定会改变数据输入的顺序;既然要交换元素的位置,那么所有元素必须要读到内存空间中,所以它会占用比较大的空间,至少能容纳整个数组;数据越多,占用的空间必然越大,海量数据处理起来相对吃力。
但是,它的时间复杂度很低,意味着数据量不大时,效率极高。
好了,两种解法写完了,赶紧实现一下吧。
<未完>