文章目录
树
二叉树的遍历
先序遍历
//递归版
void PreOrder(BiTree T) {
if(T != null) {
visit(T);
PreOrder(T.left);
PreOrder(T.right);
}
}
//非递归1
void PreOrder1(BiTree T) {
Stack s = new Stack();
BiTree cur = T;
// 可以判断T \= null
while(cur != null || !s.isEmpty()) {
if(cur != null) {
visit(cur);
s.push(cur);
cur = cur.left;
} else {
cur = s.pop();
cur = cur.right;
}
}
}
//非递归2
void PreOrder2(BiTree T) {
Stack s = new Stack();
BiTree cur = T;
if(T != null) {
s.push(T);
while(!T.isEmpty()) {
cur = s.pop();
visit(cur);
//先序是NLR,出栈与入栈逆序,所以R先入栈L后入栈
if(cur.right != null) s.push(cur.right);
if(cur.left != null) s.push(cur.left);
}
}
}
//非递归java
List<Integer> PreOrder(TreeNode root){
List<Integer> nodes = new LinkedList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while(cur != null || !stack.isEmpty()){
while(cur != null){
nodes.add(cur.val);
stack.push(cur);
cur = cur.left;
}
cur = stack.pop();
cur = cur.right;
}
return nodes;
}
中序遍历
//递归
void InOrder(BiTree T) {
if(T != null) {
InOrder(T.left);
visit(T);
InOrder(T.right);
}
}
//非递归
void InOrder(BiTree T) {
Stack s = new Stack();
Bitree cur = T;
while(cur != null || !s.isEmpty()){
if(cur != null) {
s.push(cur);
cur = cur.left;
} else {
cur = s.pop();
visit(cur);
cur = cur.right;
}
}
}
//非递归java
List<Integer> InOrder(TreeNode root){
List<Integer> nodes = new LinkedList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while(cur != null || !stack.isEmpty()){
while(cur != null){
stack.push(cur);
cur = cur.left;
}
cur = stack.pop();
nodes.add(cur.val);
cur = cur.right;
}
return nodes;
}
后序遍历
//递归
void PostOrder(BiTree T) {
if(T != null) {
PostOrder(T.left);
PostOrder(T.right);
visit(T);
}
}
//非递归
void PostOrder1(BiTree T) {
Stack s1 = new Stack();
Stack s2 = new Stack();
//先序,NLR, 后序 LRN
s1.push(T);
while(!s1.isEmpty()) {
BiTree tmp = s1.pop();
s2.push(tmp);
//先序非递归是先入栈右子树后左子树,后序非递归先左子树后右子树是因为把中间结果
//存在中间栈中,最后还要出栈顺序再次相反所以要先左后右入栈S2
//入栈前需要的遍历顺序是NLR
//入s2的顺序是NRL
if(tmp.left != null) s1.push(tmp.left);
if(tmp.right !=null) s1.push(tmp.right);
}
//出s2的顺序LRN
while(!s2.isEmpty()) {
visit(s2.pop());
}
}
//非递归2
void PostOrder2(BiTree T) {
Stack s = new Stack();
BiTree cur = T;
Bitree visited = null;
while(cur != null || !s.isEmpty()) {
if(cur != null) {
s.push(cur);
cur = cur.left;
} else {
//没有左孩子时出现在栈顶的结点,此时不能将其出栈并访问,
//因其右孩子还没被访问。应按相同的规则对其右子树进行遍历,
//当访问完其右孩子时,该结点又出现在栈顶,此时可以将其出栈并访问
cur = s.peek();
//if(cur != null && cur != visited) {
//到这步是s一定不为空,所以上面的if没必要判断cur是否为空
if(cur != visited) { //第一次出现,从左子树返回来的
cur = cur.right;
//s.push(cur);
//cur = cur.left;
} else {
cur = s.pop();
visit(cur);
visited = cur;
cur = null; //置空当前节点,以防下一轮判断时重新入栈
}
}
}
}
//非递归java
List<Integer> PostOrder(TreeNode root){
List<Integer> nodes = new LinkedList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
TreeNode pre = null;
while(cur != null || !stack.isEmpty()){
while(cur != null){
stack.push(cur);
cur = cur.left;
}
cur = stack.peek();
if(cur.right != null && cur.right != pre){
cur = cur.right;
} else {
stack.pop();
nodes.add(cur.val);
pre = cur;
cur = null;
}
}
return nodes;
}
层次遍历
void LevelOrder(BiTree T) {
Queue queue = new Queue();
queue.add(T);
while(!queue.isEmpty()) {
BiTree cur = queue.poll();
visit(cur);
if(cur.left != null) queue.add(cur.left);
if(cur.right != null) queue.add(cur.right);
}
}
例题
- 设计非递归算法求二叉树的高度,递归算法呢
//非递归
int getHight(BiTree root) {
int depth = 0;
if(root != null) {
Queue queue = new Queue();
queue.add(root);
BiTree last = root; //mask the last element of each layer
while(!queue.isEmpty()) {
BiTree cur = queue.poll();
if(cur.left != null) cur.add(cur.left);
if(cur.right != null) cur.add(cur.right);
if(cur == last) {
//按照层序遍历,当上一层的最后一个元素出对时,下一层的所有元素都刚好入队
depth++;
last = queue.lastElement();
}
}
}
return depth;
}
//递归
int getDepth(BiTree root) {
if(root == null) return 0;
int left = getDepth(root.left);
int right= getDepth(root.right);
return max(left, right) + 1;
}
- 判断二叉树是完全二叉树的算法
//完全二叉树中n个结点与满二叉树前n个结点是完全对应的
boolean isComplete(BiTree root) {
//层序遍历,把子节点为空的情况也入队,完全二叉树的层序遍历中队里不能有空
if(root != null) {
Queue queue = new Queue();
queue.add(root);
while(!queue.isEmpty()) {
BiTree cur = queue.poll();
if(cur != null) {
//入队时不判断是否为空
queue.add(cur.left);
queue.add(cur.right);
} else {
//完全二叉树只有叶结点会引入空指针且遇到空结点后队中不能再有非空结点
while(!queue.isEmpty()){
if(queue.poll() != null)
return false;
}//end while
}//end else
}//end while
}//end if
return true;
}
- 计算二叉树的所有双分支结点数
int dsonNode(BiTree root) {
if(root == null) return 0;
if(root.left != null && root.right != null)
return dsonNode(root.left) + dsonNode(root.right) + 1;
else return dsonNode(root.left) + dsonNode(root.right);
}
- 设计算法交换二叉树中所有结点的左右子树
void exchange(BiTree root) {
if(root == null) return;
exchange(root.left);
exchange(root.right);
BiTree tmp = root.left;
root.left = root.right;
root.right= tmp;
}
- 对于二叉树中每个值为x的元素,删除以他为根的子树
//java与c++不同,c需要手动释放内存
void delete(BiTree root, Type value){
if(root.val == value) {
root = null;
return;
}
delete(root.left, value);
delete(root.right, value);
}
- 查找二叉树中值为x的结点并打印其所有祖先,假设x不多于一个
//可以使用非递归后续遍历,当找到节点时,栈中的元素就是所有祖先节点
void PrintAncestors(BiTree root, Typer value) {
Stack s = new Stack();
BiTree cur = T;
Bitree visited = null;
while(cur != null || !s.isEmpty()) {
if(cur != null) {
s.push(cur);
cur = cur.left;
} else {
cur = s.peek();
if(cur.val = value){
while(!s.isEmpty()) print(s.pop().val);
return;
}
if(cur != visited) { //第一次出现,从左子树返回来的
cur = cur.right;
} else {
cur = s.pop();
visit(cur);
visited = cur;
cur = null; //置空当前节点,以防下一轮判断时重新入栈
}
}
}
}
//方式1
int flag = 0; //定义一个全局变量来判断是否查找到了值
void PrintAncestors(bittree* BT, int x) {
if (!BT)
return;
if (BT->data == x) {
flag = 1; //查询到值的时候 改变标志位
return;
}
if (flag == 0) {
PrintAncestors(BT->LChild, x); //若没有查找到往节点的左侧查找
}
if (flag == 0)
PrintAncestors(BT->RChild, x);//若没有查找到往节点的右侧查找
if (flag == 1) {
printf("%d ", BT->data);
}
}
//方式二
int PrintAncestors(bittree* BT, char x) {
if (!BT)
return 0;
if (BT->data == x) {
return 1;
}
//这个可以这样理解
//如果一颗二叉树他的左孩子有要查询的结点或者他的右孩子里面有要查询的结点那么该节点就是祖先结点
if (PrintAncestors(BT->LChild, x) || PrintAncestors(BT->RChild, x)) {
printf("%d ", BT->data);
return 1;
}
else {
return 0;
}
}
- root为二叉树根节点,p,q分别指向二叉树中任意两结点,计算p,q最近公共祖先结点
//想法一
//使用后序非递归遍历,当访问到p或者q时,栈中存的都是祖先结点,比较找到两个结点时两个栈的最近公共结点
//想法二
/*
从根节点开始遍历,如果p和q中的任一个和root匹配,那么root就是最低公共祖先。 如果都不匹配,则分别递归左、右子树,如果有一个节点出现在左子树,并且另一个节点出现在右子树,则root就是最低公共祖先. 如果两个节点都出现在左子树,则说明最低公共祖先在左子树中,否则在右子树
*/
TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//发现目标节点则通过返回值标记该子树发现了某个目标结点
if(root == null || root == p || root == q) return root;
//查看左子树中是否有目标结点,没有为null
TreeNode left = lowestCommonAncestor(root.left, p, q);
//查看右子树是否有目标节点,没有为null
TreeNode right = lowestCommonAncestor(root.right, p, q);
//都不为空,说明做右子树都有目标结点,则公共祖先就是本身
if(left!=null&&right!=null) return root;
//如果发现了目标节点,则继续向上标记为该目标节点
return left == null ? right : left;
}
- 计算非空二叉树的宽度(结点最多的一层)
int getWidth(BiTree root) {
int width = 0;
int max = 0;
if(root != null) {
Queue queue = new Queue();
queue.add(root);
BiTree last = root; //mask the last element of each layer
while(!queue.isEmpty()) {
BiTree cur = queue.poll();
width++;
if(cur.left != null) cur.add(cur.left);
if(cur.right != null) cur.add(cur.right);
if(cur == last) {
//按照层序遍历,当上一层的最后一个元素出对时,下一层的所有元素都刚好入队
max = max(max, width);
width = 0;
last = queue.lastElement();
}
}
}
return max;
}
- 将二叉树的叶节点按从左到右连城单链表,用叶节点的右指针做单链表的指针
LinkedList head, pre = null;
LinkedList InOrder(BiTree root) {
if(root != null) {
InOrder(root.left);
if(root.left == null && root.right == null) {
if(pre == null) {
head = root;
pre = root;
} else {
pre.right = root;
pre = root;
}
}
InOrder(root.right);
return head;
}
}
线索二叉树的构建与遍历
构建(中序为例)
void InThread(BiTree cur, BiTree pre) {
if(cur != null) {
InThread(cur.left);
if(cur.left == null) {
cur.left = pre;
cur.lflag = 1; //1 is pre or next, 0 is l/r subnode
}
if(pre != null && pre.right == null){
pre.right = cur;
pre.rflag = 1;
}
//对于右子树,当前节点为前驱
pre = cur;
InThread(cur.right);
}
}
遍历
//找中序中的第一个节点
ThreadNode FistNode(ThreadNode root) {
while(root.lfalg == 0) root = root.left;
return root;
}
//找到当前节点的后继结点
ThreadNode NextNode(ThreadNode cur) {
if(cur.rflag == 1) return cur.right;
else return FirstNode(cur.right);
}
//遍历所有结点
void InOrderThread(ThreadNode root) {
for(ThreadNode cur = FirstNode(root); cur != null; cur = NextNode(cur)) {
visit(cur);
}
}
二叉排序树
插入
boolean insertBst(BiTree root, Type value) {
if(root == null) {
root = new BSTNode(value);
root.left = root.right = null;
return ture;
} else if{root.val == value} {
return true;
} else if(root.val > value) {
return insertBst(root.left, value);
} else {
return insertBst(root.right, value);
}
}
查找
//递归
boolean find(BiTree root, Type value) {
if(root != null) {
if(root.val == value) return true;
else if(root.val < value) return find(root.right, value);
else return find(root.left, value);
}
return false;
}
//非递归
boolean find(BiTree root, Type value) {
while(root != null && root.val != value) {
if(root.val < value) root = root.right;
else root = root.left;
}
if(root.val == value) return true;
return false;
}
删除
//二叉排序树的删除分三种情况
//待删结点是叶节点,直接删除
//待删结点只有左子树或者右子树,将待删结点的父节点作为其左右子树的父节点
//待删结点同时包含左右子树,用待删结点的后继或者前驱(中序)替换之,删除替换的那个结点
Status DeleteBST(BiTree &T, KeyType key){
//若二叉排序树T中存在关键字等于key的数据元素,则删除该数据元素结点
//并返回TURE,否则返回FALSE
if(!T) return FALSE; //根或子树为空, 未找到key,删除失败
else {
if(EQ(key,T->data.key)) return Delete(T); //找到key,进行删除操作
else if(LT(key,T->data.key)) return DeleteBST(T->lchild,key); //向右子树查找
else return DeleteBST(T->rchild,key); //向左子树查找
}
}
Status Delete(BiTree &p){
//从二叉排序树中删除结点p,并重接它的左子树或右子树
if(!p->rchild){//右子树空,则只需重接它的左子树
q = p; p = p->lchild;free(q);
}
else if(!p->lchild){//左子树空,则只需重接它的右子树
q = p; p = p->rchild;free(q);
}
else{//左右子树均不空
q = p; s = p->lchild;
while(s->rchild){q=s; s=rchild;} //找到p的左子树中最接近p值的s(此时s->rchild==NULL)
p->data = s->data; //s的值替换p的值
if(q!=p) q-rchild = s->lchild; //重接*q的右子树 (p->lchild->rchild!=NULL)
else q->lchild = s->lchild; //重接*q的左子树 (p->lchild->rchild==NULL)
}
}
例题
- 判定二叉树是否为二叉排序树
//二叉树的先后遍历都行,如下以中序为例
Type pre = -inf;
boolean isBst(BiTree root) {
if(root == null) return true;
else {
boolean subLeft = isBst(root.left);
//如果左子树不满足或者当前结点小于前驱结点
if(subLeft || root.val > pre) return false;
pre = root.val;
boolean subRight = isBst(root.right);
return subRight;
}
}
- 计算指定结点在给定二叉排序树中的层次
int level(BiTree root, Type value) {
int n = 0;
while(root != null) {
if(root.val == value) return n;
else if(root.val < value) root = root.right;
else root = root.left;
n++;
}
}
- 判断二叉树是否为平衡二叉树
boolean isAvl(BiTree root) {
boolean balance = false;
getDepth(root, balance);
return balance;
}
int getDepth(BiTree root, boolean isBalance) {
if(root == null) return 0;
int leftDepth = getDepth(root.left);
int rightDepth = getDepth(root.right);
if(abs(leftDepth - rightDepth) > 1) isBalance = false;
return max(leftDepth, rightDepth) + 1;
}
图
广度遍历
void BFS(Graph G, Node v){
Queue queue = new Queue();
v.isVisited = true;
queue.add(v);
while(!queue.isEmpty()) {
Node cur = queue.poll();
for(w = FirstNode(v); w != null; w = NextNeighbor(v,w)) {
if(!w.isVisited) {
w.isVisited = true;
queue.add(w);
}
}
}
}
深度遍历
void DFS(Graph G, Node v){
v.isVisited = true;
for(w = FirstNode(v); w != null; w = NextNeighbor(v,w)) {
if(!w.isVisited) {
DFS(G, w);
}
}
}
//非递归
void DFS(Graph G, Node v){
Stack stack = new Stack();
v.isVisited = true;
stack.push(v);
while(!stack.isEmpty()){
Node w = stack.pop();
w.isVisited = true;
for(k = FirstNode(w); k != null; k = NextNeighbor(k,w)){
if(!k.isVisited){
k.isVisited = true;
stack.push(k);
}
}
}
}
Dijstra(单源最短路径)
/*
Dijkstra算法将图中结点分为两类,已访问V和未访问T,
每次以新加入V的结点为中间节点,遍历T找到距离最小的结点w
将w加入V后更新T中所有的距离,如果经过从源点经w后到其他未被访问结点距离更小则更新
*/
/*
* 参数adjMatrix:为图的权重矩阵,权值为-1的两个顶点表示不能直接相连
* 返回顶点0到其它所有顶点的最短距离,其中顶点0到顶点0的最短距离为0
*/
public int[] Dijkstra(int[][] adjMatrix) {
//用于存放顶点0到其它顶点的最短距离
int[] result = new int[adjMatrix.length];
//用于判断顶点是否被遍历
boolean[] used = new boolean[adjMatrix.length];
used[0] = true;
//初始化源点到直接相连结点的距离
for(int i = 1;i < adjMatrix.length;i++) {
result[i] = adjMatrix[0][i];
used[i] = false;
}
//对于所有未被访问的结点
for(int i = 1;i < adjMatrix.length;i++) {
int min = Integer.MAX_VALUE; //用于暂时存放顶点0到i的最短距离,初始化为Integer型最大值
int k = 0;
//找到顶点0到其它顶点中距离最小的一个顶点
for(int j = 1;j < adjMatrix.length;j++) {
if(!used[j] && result[j] != -1 && min > result[j]) {
min = result[j];
k = j;
}
}
used[k] = true; //将距离最小的顶点,记为已遍历
//以k为中间顶点更新最短距离
for(int j = 1;j < adjMatrix.length;j++) {
if(!used[j]) { //当顶点j未被遍历时
//首先,顶点k到顶点j要能通行;这时,当顶点0到顶点j的距离大于顶点0到k再到j的距离或者顶点0无法直接到达顶点j时,更新顶点0到顶点j的最短距离
if(adjMatrix[k][j] != -1 && (result[j] > min + adjMatrix[k][j] || result[j] == -1))
result[j] = min + adjMatrix[k][j];
}
}
}
return result;
}
Floyd(多源最短路径)
/*
对于图中的所有结点对<i,j>,使用图中的每个结点依次作为中间结点k,如果经过中间结点的
距离使原来结点对之间的距离更小则更新即d[i][j] = d[i][k] + d[k][j]
*/
void Floyd(int[][] d, int start, int end){
for(k=1;k<=n;k++)
for(i=1;i<=n;i++)
for(j=1;j<=n;j++) {
if(d[i][k]+d[k][j]<d[i][j]) {
d[i][j]=d[i][k]+d[k][j];
//path[i][j]=path[i][k];
}
}
//while (start!=end) {
// printf("%d->",start);
// start = path[start][end];
//}
}
排序
1. 插入排序
1.1 直接插入排序
//将待排序列中的元素插入有序序列适当位置
//如下函数要求数据从1开始存放
void insertSort(Type[] data){
int len = data.length;
//data[0]为哨兵,不存放元素
for(int cur = 2; cur < len; ++cur) {
//当前元素(未排序)比前一个已排序的大,需要在前面有序序列中找到合适位置插入
if(data[cur]< data[cur-1]){
//为防止越界,设置哨兵,反向比较
data[0] = data[cur];
int j = cur-1;
for(; data[j] > data[0]; j--){
data[j+1] = data[j];
d
}
data[j+1] = data[0];
}
}
}
//不设置哨兵
void insertSort(Type[] data){
int len = data.length;
for(int cur = 1; cur < len; ++cur) {
if(data[cur] < data[cur-1]){
Type tmp = data[cur];
int j = cur-1;
for(; tmp > data[j] && j >= 0; j--){
data[j+1] = data[j];
}
data[j+1] = tmp;
}
}
}
1.2 折半插入排序
//折半插入是把直接插入在有序部分搜索转化为折半查找
void halfInserSort(Type[] data){
int len = data.length;
for(int cur = 1; cur < len; ++cur){
//查找合适的位置
int low = 0, high = cur-1;
while(low <= high){
int mid = (low+high)/2;
if(data[cur] > data[mid]) low = mid+1;
if(data[cur] < data[mid]) high = mid-1;
}
//因为是升序,while结束后high指向待插位置下一位置
//移动
for(int j = cur-1; j >= high+1; j--)
data[j+1] = data[j];
data[high+1] = data[cur];
}
}
1.3 希尔排序
//希尔排序的过程是在待排序列中按照指定步长挑选元素,对挑选的元素做直接插入排序
//L[i, i+d, i+2d,...,i+kd],步长逐次递减,一般折半递减
void shellSort(Type[] data){
int len = data.length;
//对每个步长执行插入排序
for(int step = len/2; step >= 1; step /= 2){
//对未排序部分排序
//每个区间的第一个未排序结点从step+1开始
for(int i = step+1; i <= len; i++){
if(data[i] < data[i-step]){
Type tmp = data[i];
//从i前一个结点开始向前,每次跳step个元素找到下一个
int j = i-step;
for(int j = i-step; j > 0 && data[j]<tmp; j -= step){
data[j+step] = data[j];
}
data[j+step] = tmp;
}
}
}
}
2. 交换排序
2.1冒泡排序
void bubbleSort(Type[] data){
int len = data.length;
//只需要比较len-1次
for(int i = 0; i < len-1; i++){
boolean isChange = false;
for(int cur = len-1; cur > i; cur--){
//每次比较将最小的放到一端
if(data[cur] < data[cur-1]){
//swap
Type tmp = data[cur];
data[cur] = data[cur-1];
data[cur-1] = tmp;
isChange = true;
}
}
if(!isChange) break;
}
}
//双向气泡,奇数趟将最大元素移到尾部,偶数趟将最小元素移到头部
void dualBubbleSort(Type[] data){
int low = 0, high = data.length-1;
boolean flag = true;
while(low<high && flag){
flag = false;
for(int i = low; i < high; i++){
//high = data.length-1,当i=high-1, i+1 = high= data.lenght-1,可取
if(data[i] > data[i+1]){
swap(data[i], data[i+1]);
flag = true;
}
//找到最大元素,区间减小
high--;
for(int i = high; i > low; i--){
if(data[i] < data[i-1]){
swap(data[i], data[i-1]);
flag = true;
}
}
//找到最小元素,区间减小
low--;
}
}
}
//大数上浮
for (i = 0; i < len - 1; i++)
for (j = 0; j < len - 1 - i; j++)
if (arr[j] > arr[j + 1])
{
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
2.2 快速排序
//在待排序列中任取一值做为pivot,一趟排序后将小于pivot的元素放在pivot的左边,大于pivot的放在右边,一趟排序后pivot找到最终位置,后递归遍历两个子表
void quickSort(Type[] data, int low, int high){
if(low < high){
int pivot = partition(data, low, high);
quickSort(data, low, pivot-1);
quickSort(data, pivot+1, high);
}
}
int partition(Type[] data, int low, int high){
//在序列中将大于p的放在右边,小于p的放在左边,最终剩下的位置就是p的最终位置
//p左右两侧的顺序不一定有序
//枢轴可以取任意值
Type p = data[low];
while(low < high){
//比枢轴大则指针左移
while(low < high && data[high] > p) high--;
//将小于枢轴p的值存起来,可以用任意变量,data[low]位置可用不必创建新的变量
data[low] = data[high];
//比枢轴小则指针右移
while(low < high && data[low] < p) low++;
//将左边大于枢轴的值放在右边小于枢轴的位置
data[high] = data[low];
}
//当上面的while结束后,low位置就是枢轴的位置
data[low] = p;
return low;
}
3. 选择排序
3.1简单选择排序
//每次在未排序序列中找到最小的和未排序序列第一个元素交换
void selectSort(Type[] data){
for(int i = 0; i < data.length-1; i++){
//只需要比较len-1次
int minIndex = i;
for(int j = i+1; j < data.length; j++){
if(data[j] < data[minIndex]) minIndex = j;
}
if(minIndex != i){
swap(data[minIndex], data[i]);
}
}
}
3.2 堆排序
//堆排序是一种树形选择排序算法,其将待排序序列视作完全二叉树的顺序存储
//树的每棵子树根节点都大于或小于子节点,子节点间没有相对关系
//利用完全二叉树的性质选择排序
//构建大顶堆
void buildMaxHeap(Type[] data){
//最后一个非叶节点位置len/2,依次对所有非叶节点做调整
int len = data.length;
for(int i = len/2; i > 0; i++)
adjustDown(data, i);
}
//从下向上调整
void adjustDown(Type[] data, int index){
//第0个元素不用,使用完全二叉树性质,索引从1起
data[0] = data[index];
//从index的叶节点开始一次向下比较是否都满足,乘以2就找到当前元素的左子节点
for(int i = index*2; i < data.length; i *= 2){
//选择子结点中值最大的那个的索引
if(i < data.length && data[i] < data[i+1]) i++;
//判断当前结点是否可以作为子树的根节点
if(data[0] > data[i]) break;
else {
//当根节点不满足大顶堆性质时,交换根结点和子结点,继续判断子树
data[index] = data[i];
index = i;
}
}
//如果发生过交换,最后剩下的位置就是要找的位置
data[index] = data[0];
}
//从上向下调整
void adjustUp(Type[] data, int index){
//第0个元素不用,使用完全二叉树性质,索引从1起
data[0] = data[index];
int parent = index / 2;
while(parent > 0 && data[parent] < data[0]){
//双亲结点比子节点小,交换,父节点下来
data[index] = data[parent];
index = parent; //新位置指向父节点
parent /= 2; //父节点指向父节点的父节点
}
data[index] = data[0];
//如果发生过交换,最后剩下的位置就是要找的位置
data[index] = data[0];
}
void heapSort(Type[] data){
buildMaxHeap(data);
//只需要len-1次比较
for(int i = data.length; i > 1; i--){
//输出堆顶元素,堆顶与堆尾交换
swap(data[i], data[1]);
//重新调整堆
adjustDown(data, 1);
}
}
4.归并排序(nlogn)
int[] sort(int[] sourceArray) throws Exception {
if (sourceArray.length < 2) return sourceArray;
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
int middle = (int) Math.floor(arr.length / 2);
int[] left = Arrays.copyOfRange(arr, 0, middle);
int[] right = Arrays.copyOfRange(arr, middle, arr.length);
return merge(sort(left), sort(right));
}
int[] merge(int[] left, int[] right) {
int[] result = new int[left.length + right.length];
int i = 0;
while (left.length > 0 && right.length > 0) {
if (left[0] <= right[0]) {
result[i++] = left[0];
//把第一个元素去掉
left = Arrays.copyOfRange(left, 1, left.length);
} else {
result[i++] = right[0];
right = Arrays.copyOfRange(right, 1, right.length);
}
}
while (left.length > 0) {
result[i++] = left[0];
left = Arrays.copyOfRange(left, 1, left.length);
}
while (right.length > 0) {
result[i++] = right[0];
right = Arrays.copyOfRange(right, 1, right.length);
}
return result;
}
5. 比较
时间复杂度
简单选择排序,直接插入排序,冒泡排序平均时间复杂度都为O(n^2),当初始状态接近有序时,直接插入排序和冒泡排序最好时间复杂度可达到O(n)。而简单选择与初始状态无关。
快排和归并都是基于分治,时间复杂度都是(nlogn),快排在最坏情况下可达到O(n^2)
空间复杂度
简单选择排序,插入排序,冒泡排序,希尔排序和堆排序都使用常数个辅助空间
快排平均使用O(logn),最坏为O(n)
归并排序为O(n)
稳定性
插入排序,冒泡排序,归并排序和基数排序是稳定排序
简单选择排序,快排,希尔排序,堆排序是不稳定排序
过程结果
冒泡排序,堆排序每次都会产生本次循环的最大值或者最小值
快排每次循环确定一个元素最终位置