数据结构和高数教程
教程 数据结构
数据结构大纲脑图
一、数据结构和高数
学习工具-数据结构演示系统(C版)
- 数据结构
程序设计=数据结构+算法
数据结构:
1-1. 逻辑结构:逻辑关系抽象模型
集合模型:元素关系松散
线性结构:元素关系只有一对一关系
树型关系:元素1对多,并且存在层次关系
图形结构:元素多对多关系,也叫网状结构.
1-2. 物理结构:计算机存储形式主要有两种
顺序存储:存放在连续的存储单元,元素关系逻辑结构与存储结构一致
链式存储:逻辑关系无法直接反应元素关系,需要指针表达.
1-3. 算法
算法是计算机解决问题的步骤:输入,输出,有穷,确定,可行性;数据结构是算法的操作对象.
时间复杂度:函数的渐进增长T(n)=O(f(n))
T(n)语句总的执行次数(时间);f(n)是规模n的某个函数
随着n的增大T(n)增长最慢的算法最优.
函数:随着算法的数据规模增长与所用时间的关系
所用时间与算法核心循环迭代执行次数(数据规模)
1-3-1. 判断一个算法效率:函数常数和次要项可以忽略,而应该关注最高项的阶数.
2. 常见时间复杂度
2-1. O(1):高斯求累加,执行次数T(n)是常数;O(n)循环一次;O(n2):嵌套循环两次;O(n3):嵌套循环3次
循环的时间复杂度=循环体的时间复杂度*循环运行的次数
**O(logn)**如下:
int i=1,n=100;
while(i<n){
i=i*2;
}
函数2^x=n得到x=log(2)n所以这个循环的时间复杂度按照以下攻略转换成O(logn)
2-2. 比较排序如下:O(1)<O(log2n)<O(n)<O(nlog2n)<O(n2)<O(n3)…<O(2n)<O(n!)<O(nn)**
2019-04-25_12-18.png
2019-04-25_12-18.png
2-3. 函数转化成时间复杂度方法:如fn(3n2+4n+5)=>O(n2)**
用常数1取代运行时间中的所有加法常数
修改后的运行次数函数中,只保留最高阶项
如果最高阶项存在且不是1,则去除这个项的相乘常数
得到的结果就是大O阶时间复杂度
最坏情况(时间复杂度多指最坏)和平均情况
空间复杂度:空间换时间,如:立flag
S(n)=O(f(n))
n是问题规模,f(n)是语句关于n所占空间的函数
- 递归思想-函数格式-效率
3-1. 递归在计算机科学中指通过重复问题分解为同类子问题而解决问题的办法。
思想:是把规模大的问题转化为规模小的相似的子问题来解决,在函数实现时,因为大问题和小问题的解决方法是同一个方法,就产生来函数调用自身的情况。
如:求阶乘
public long fact(int n){
if(n==1){
return 1;
}
return fact(n-1)*n;//函数自调用
}
调用形式有两种:直接递归调用函数中出现调用函数本身,间接递归调用(函数中调用来其他函数,其他函数又调用了本函数)。
3-1-1. 如:斐波那契数列第n项:斐波那契数列第一项和第二项是1,后面每一项是前两项之和,即1,1,2,3,5,…
public long fib(int x){
if(x>2){
return fib(x-1)+fib(x-2);//直接递归
}else return 1;
}
//可以这样
public static int add(int n){
if(n==1) return 1;
else return add(n-1)+n;
}
递归的条件:一定要有明确的结束条件,可以没有返回值;否则会造成无线循环死循环造成栈溢出。
代码中先明确递归条件再调用递归,否则结束条件可能走不到。
大部分递归函数都能用非递归函数的循环来解决。但是汉诺塔问题就不好递归解决。
递归可读性好,但是耗资源。
效率:
改进的斐波那契数列:缩小来问题规模从求第n项变成来求n-1项。
2019-04-27_18-52.png
2019-04-27_18-52.png
//a,b是数列的开头两项,第三个单数是我们想求的以前两个参数开头的数列的第几项
public int fib(int a,int b,int n){
if(n==3){
return a+b;
}else{
return fib(b,a+b,n-1)
}
}
函数压栈弹栈调用不仅浪费空间还浪费时间。而迭代可能跟函数一样第次数,但是效率更高。
3-1-1-1. 兔子问题:n=(n-1)+(n-2)就是斐波那契
2019-04-27_19-02.png
2019-04-27_19-02.png
- 1. 顺序表
4-1. 在顺序表中插入一个数据元素(ins_sqlist)
package datastruct.shunxubiao;
import java.lang.reflect.Array;
import java.util.ArrayList;
/**
-
majia
-
19-4-25
-
顺序表插入
*/
public class charu {
public static void main(String[] args) {
String[] v={“a”,“j”,“c”,“n”,“i”,“y”,“d”,“u”,“?”};//数组
int i=3;//要插入的位置
String b=“p”;//要插入的内容
int maxlen=16;//数组最大长度
System.out.println(v.length);
// 在顺序线性表L中第i个位置之前插入新的元素e
// i的合法值为1<i<=List.length_sq(v)+1
if(i<1||i>v.length-1){
return;
}
if(v.length-1>=maxlen){
System.out.println(“overflow”);
}else{
for(int j=(v.length-2);j>=i;–j){//数组从0开始
v[j+1]=v[j];// 插入位置之后的元素右移!!!
}
v[i]=b;//插入b,表长赠一
}
for (int k=0;k<v.length;k++){
System.out.print(v[k]);
}
}
}
4-2. 删除顺序表中一个数据元素(del_sqlist)
public static void main(String[] args) {
String[] v={“a”,“j”,“c”,“n”,“i”,“y”,“d”,“u”,“?”};//数组
int i=3;//要插入的位置
System.out.println(v.length);
// 在顺序线性表L中删除第i个位置元素
// i的合法值为1<i<=List.length-1
if(i<1||i>v.length-1){
return;
} else{
for(int j=i+1;j<v.length;++j){//数组从0开始
v[j-1]=v[j];//i后的元素前移!!!
}} System.out.println(v.length); //表长减一,因为lenth是final没办法改 for (int k=0;k<v.length-1;k++){ System.out.print(v[k]); }
}
4-3. 合并两个有序顺序表(merge_sqlist)
/**- 已知顺序线性表,va,vb中元素依值升序排列,
- 本算法归并这两个表,得到一个新表vc
- vc中的元素也依值升序排列
- @param args
*/
public static void main(String[] args) {
char[] va={‘a’,‘c’,‘d’,‘i’,‘j’,‘t’,‘u’,‘y’};
char[] vb={‘c’,‘f’,‘k’,‘l’,‘n’,‘s’};
char[] vc=new char[20];
int i=0,j=0,k=0;
while(i<va.length&&j<vb.length){
// 归并
if(va[i]<=vb[j]){
vc[k]=va[i];
k++;
i++;
}else{
vc[k]=vb[j];
k++;
j++;
}
}
// 以下两个while注定只能执行一个
while(i<va.length){
// 插入va剩余字段
vc[k]=va[i];
k++;
i++;
}
while(j<vb.length){
// 插入vb剩余字段
vc[k]=va[j];
k++;
j++;
}
for (int a=0;a<vc.length;a++){
System.out.print(vc[a]);
}
}
- 2. 链表
链表:逻辑连续,物理不连续;一系列节点组成;节点包含数据域合指针域组成.分为:单向,双向,循环链表。
head指向带个表头节点,终止与指向null的指针。
5-1. 创建一个单链表(Crt_LinkList)
5-1-1. 头插法
package datastruct.lianbiao;
/**
-
majia
-
19-4-25
*/
public class ChuangJianLianBiao {
public static void main(String[] args) {
// 创建链表方式1头插法
//指定链表的长度
// SingleListNode list = createList(5);
// 给定数组,根据数组的元素,创建对应的链表
int[] arr = {1, 9, 2, 8, 7, 4, 5};
SingleListNode list = createListHead(arr);
// 使用尾插法创建链表
// 给定数组,根据数组的元素,创建对应的链表
// int[] arr = {1, 9, 2, 8, 7, 4, 5};
// SingleListNode list = createListTail(arr);
// 打印链表
printInNode(list);//头插法:将链表到右端看成固定的,链表不断向左延伸,最先得到的是尾节点
}
}
5-1-2. 尾插法创建
/**- 使用尾插法创建链表
- @param arr
- @return
/
private static SingleListNode createListTail(int[] arr) {
SingleListNode head = null;
SingleListNode tail = null;
for (int i = 0; i < arr.length; i++) {
SingleListNode node = new SingleListNode(arr[i]);
if (head == null) {//如果没有头节点。就把新创建到节点作为头节点
head = node;
} else {
tail.next = node;//尾指针指向node;添加一个节点
}
tail = node;//指针移动到node节点,把新创建到节点作为尾节点
}
// 循环结束后,tail是否为空
if (tail != null) {
tail.next = null;//最终到链表尾指针指向null
}
return head;
}
5-1-3. 遍历链表
/* - 递归遍历链表-一次打印一个节点
- @param list
/
private static void printInNode(SingleListNode list) {
if (list != null) {//链表有元素
SingleListNode node = list.next;//通过指针获取元素
if (list.next != null) {//如果有下一个节点值
System.out.print(list.val + “->”);
} else {//到尾节点了
System.out.println(list.val);
}
// 递归到打印
printInNode(node);
}
}
5-1-4. 其他创建方法
/* - 根据指定的长度创建链表-头插法
- @param i
/
private static SingleListNode createList(int i) {
//创建节点,同时赋予值
SingleListNode tail = new SingleListNode(9);
for (int j = 0; j < i; j++) {
int val = j;
//创建节点
SingleListNode head = new SingleListNode(val);//每创建一个节点,就放在当前链表的最前面
head.next = tail;//把两个节点连接上,新创建的节点在链表的头
tail = head;//指针指向head元素。
}
return tail;
}
/* - 根据指定的数组创建链表-头插法
- @param
/
private static SingleListNode createListHead(int[] arr) {
// 为了实现数据到顺序合数组到顺序一致
SingleListNode tail = new SingleListNode(arr[arr.length - 1]);
for (int i = arr.length - 2; i >= 0; i–) {
SingleListNode head = new SingleListNode(arr[i]);
head.next = tail;
tail = head;//指针移动到head
}
return tail;
}
}
/*
-
链表的操作对象,也就是节点
*/
class SingleListNode {
int val;//数据域
SingleListNode next;//指针域public SingleListNode(int val) {
this.val = val;
}
}
5-2. 在单链表中插入一个结点(Ins_LinkList)
5-3. 删除单链表中的一个结点**(Del_LinkList**)
/**- 删除链表中指定的元素-非递归方式
- @param
- @param i
*/
private static SingleListNode removeNode(SingleListNode head, int i) {
if(headnull){//空链表
return head;//默认链表用头节点表示
}
SingleListNode p=head;
while(p.next!=null){//不是最后一个节点
if(p.next.vali){//相等找到了要删除的点,直接把连接指向下下一个节点
p.next=p.next.next;
}else{//偏移指针,遍历下一个节点
p=p.next;
}
}
//判断头节点,如果相等,就返回头的next
return head.val==i?head.next:head;
}
5-4. 两个有序链表求并(Union)
5-5. 归并两个有序链表(MergeList_L)
5-6. 两个有序链表求交(ListIntersection_L)
5-7. 两个有序链表求差(SubList_L)
5-8. 双向链表(前驱后继)
package datastruct.lianbiao;
/**
-
majia
-
19-4-27
-
双向链表基本操作
-
为了代码规范使用泛型
/
public class ShuangXiangLianBiao {
/*-
内部类定义链表的数据结构
*/
private class ShuangXiangNode {
// 定义数据域
private T data;
// 前驱:上一个节点的引用
private ShuangXiangNode pre;
// 后继:下一个节点的引用
private ShuangXiangNode next;// 构造器
public ShuangXiangNode() {
}public ShuangXiangNode(T data, ShuangXiangNode pre, ShuangXiangNode next) {
this.data = data;
this.pre = pre;
this.next = next;
}
}
// 保存链表的头节点
private ShuangXiangNode head;
// 保存链表的尾节点
private ShuangXiangNode tail;
// 链表的长度(链表节点数)
private int size;// 基本操作类的构造方法
public ShuangXiangLianBiao() {
// 空链表tail,head都是null
head = null;
tail = null;
}//以指定数据元素创建链表,只有一个元素的链表
public ShuangXiangLianBiao(T ele) {
head = new ShuangXiangNode(ele, null, null);
tail = head;
//添加节点后 ,链表的长度增加
size++;
}/**
- 定义链表操作基本操作方法
- 如:链表长度,链表是否为空,清空链表,添加节点(头插法,尾插法
- 获取索引index,所在位置元素
- 向指定位置插入元素
- 删除指定索引位置的元素
- 打印链表:(从前往后|从后往前)
/
/* - 求长度
- @return
*/
public int length() {
return size;
}
/**
- 是否为空
*/
public boolean empty() {
return size == 0;
}
/**
- 清空链表
*/
public void clear() {
head = null;
tail = null;
size = 0;
}
/**
- 头插法添加节点
*/
public void addHead(T ele) {
//如果插入前,链表是空的
if (tail == null) {
// 创建新的节点,让心结点指针的next指针指向原来的head,新节点作为新head
head = new ShuangXiangNode(ele, null, null);
tail = head;
} else {
// 创建新的节点,让新结点指针的next指针指向原来的head,新节点作为新head
ShuangXiangNode newNode = new ShuangXiangNode(ele, null, head);
// 与上一步原子操作,原来头节点到pre指向新节点
head.pre = newNode;
// 心结点作为头节点
head = newNode;
}
//链表长度
size++;
}
/**
- 尾插法添加节点
*/
public void addTail(T ele) {
// 如果链表时空
if (head == null) {
head = new ShuangXiangNode(ele, null, null);
//只有一个节点,head,tail都指向该即改节点
tail = head;
} else {
//创建新的节点,然后新节点的pre指向原来tail的节点
ShuangXiangNode newNode = new ShuangXiangNode(ele, tail, null);
//让尾节点的next指向新增的节点
tail.next = newNode;
// 新节点作为尾节点
tail = newNode;
}
size++;
}
/**
- 从前往后打印链表节点值
- @param
*/
public void printFromHead() {
System.out.println(“head->tail:”);
ShuangXiangNode data = head;
while (data != null) {
if (data.next != null) {//链表还有next
System.out.print(data.data.toString() + “->”);
} else {//到了最后一个节点
System.out.print(data.data.toString());
}
// 指针向后偏移
data = data.next;
}
System.out.println();
}
/**
- 从后向前遍历链表
*/
public void printFromTail() {
System.out.println(“tail->head”);
ShuangXiangNode data = tail;//从尾节点向前迭代
while (data != null) {
if (data.pre != null) {//链表还有pre
System.out.print(data.data.toString() + “->”);
} else {//到了最后一个节点
System.out.print(data.data.toString());
}
// 指针向后偏移
data = data.pre;
}
System.out.println();
}
public static void main(String[] args) {
final ShuangXiangLianBiao list = new ShuangXiangLianBiao<>();
list.addHead(“aaa”);
list.addHead(“bbb”);
list.addTail(“ccc”);
list.addTail(“ddd”);
// System.out.println(“链表是否为空:” + list.empty());
// System.out.println(“链表的长度:” + list.length());
// list.printFromHead();
// list.printFromTail();
// System.out.println(list.getNodeById(2).data);
// list.insertNode(“nab”, 0);
// list.insertNode(“nn”, 5);
list.printFromHead();
list.delete(0);
list.printFromHead();
list.removeTail();
list.printFromHead();
list.delete(1);
list.printFromHead();
}/**
- 根据索引inde来获取指定位置的节点
*/
public ShuangXiangNode getNodeById(int index) {
// 参数校验
if (index < 0 || index > size - 1) {
throw new IndexOutOfBoundsException(“索引越界”);
}
// 可以从两个方向进行迭代,可以借鉴二分法
if (index <= size / 2) {
ShuangXiangNode cur = head;
for (int i = 0; i <= size / 2 && cur != null; i++, cur = cur.next) {
if (i == index) {//找到索引所在点节点
return cur;
}
}
} else {//索引位于有半区,从tail开始迭代
ShuangXiangNode cur = tail;
for (int i = size - 1; i > size / 2 && cur != null; i–, cur = cur.pre) {
if (i == index) {
return cur;
}
}
}
return null;
}
/**
- 在指定位置插入元素
*/
public void insertNode(T ele, int index) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException(“索引越界”);
}
if (head == null) {
addTail(ele);
} else {
if (index == 0) {//表头插入
addHead(ele);
} else if (index == size) {//如果表尾
addTail(ele);
} else {
//获取插入点之前的一个节点和插入点的节点
ShuangXiangNode pre = getNodeById(index - 1);
ShuangXiangNode next = pre.next;
//让心结点的next应用next节点,pre指向pre节点
ShuangXiangNode newNode = new ShuangXiangNode(ele, pre, next);
//pre的next指向新节点,next的pre指向心结点。
pre.next = newNode;
next.pre = newNode;
// 链表长度增加
}
}
size++;
}
/**
- 删除最后一个元素
*/
public T removeTail() {
return delete(size-1);
}
/**
- 删除元素
*/
public T delete(int index) {
if (index < 0 || index > size - 1) {
throw new IndexOutOfBoundsException(“索引下标越界”);
}
ShuangXiangNode del = null;
// 如果要删除的是头节点
if (index == 0) {
del = head;
head = head.next;
head.pre = null;//头节点的pre位null
} else if (index == size - 1) {//要删除的是尾节点
del = tail;
tail = tail.pre;
tail.next = null;
} else {//删除的是中间节点
ShuangXiangNode pre = getNodeById(index - 1);
// 获取要删除的节点
del = pre.next;
// 前节点的next指向被删除的下一个节点
pre.next=del.next;
if (del.next != null) {
// 让被删除的节点的下一个节点的pre指向被删除的节点的pre节点
del.next.pre = del.pre;
}
// 被删除节点的前驱后继必须都位null,断开引用
del.next=null;
del.pre=null;
}
size–;
return del.data;
}
}
5-9. 循环链表
循环链表主要有单向循环链表和双向循环链表:其结构特点是tail的next不在是null,而是指向整个链表的head节点。
-
2019-04-27_16-40.png
2019-04-27_16-40.png
package datastruct.lianbiao;
/**
-
majia
-
19-4-27
-
循环链表
*/
public class CycaLinkList {
// 创建一个内部节点类
private class Node {
// next引用
private Node next = null;
// 值
private T value = null;// 构造方法 public Node() { } public Node(T value) { this.value = value; }
}
// 创建一个头节点
private Node head = null;// 构造方法,对头节点初始化
public CycaLinkList() {
head = new Node(null);
head.next = head;//next指向自己
}
/**- 先定义好操作对基本方法
- 添加元素,打印元素
- 获取链表长度
- 查找指定位置对节点值
/
/* - 向链表尾插入一个元素
- @param value
/
public void addTail(T value){//尾插法
Node node = new Node(value);
if(head.next==head){//空链表-单节点链表
head.next=node;
node.next=head;
}else{//找到尾节点
Node tmp=head;
while(tmp.next!=head){//如果不是尾节点,就一致迭代
tmp=tmp.next;
}
//找到尾节点
tmp.next=node;//尾节点的next指向新节点
node.next=head;//新节点的next指向头节点
}
}
/* - 打印元素
*/
public void print(){
Node tmp=head;
while(tmp.next!=head){
if(tmp.next.next!=head){
System.out.print(tmp.next.value+“->”);
}else{
System.out.println(tmp.next.value);
}
tmp=tmp.next;
}
System.out.println();
}
/**
- 获取链表的长度
- @return
*/
public int getLength(){
Node tmp=head;
int size=0;
// 开始迭代链表
while (tmp.next!=head){
size++;
tmp=tmp.next;
}
return size;//返回长度
}
/**
- 根据位置获取节点值
- @param index
- @return
*/
public T getValueByIndex(int index){
if(index<0||index>=getLength()){
throw new IndexOutOfBoundsException(“索引越界”);
}
Node tmp=head;
int count=0;
Node node = new Node();
while (tmp.next!=head){
if(count==index){//找到了位置,跳出循环
node.value=tmp.next.value;
break;
}
// 没有找到
count++;
tmp=tmp.next;//指针向后移动
}
//返回找到的值
return node.value;
}
/**
- 根据值找到它的位置
- @param value
- @return
*/
public int getIndexByValue(T value){
int index=0;
Node tmp=head;
while(tmp.next!=head){
if(tmp.next.value.equals(value)){//找到了
return index;
}
index++;
tmp=tmp.next;
}
return -1;
}
/**
- 根据值来删除指定的节点
- @param
*/
public void deleteNode(T value){
Node tmp=head;
while (tmp.next!=head){
if(tmp.next.value.equals(value)){//找到了该值的节点
//干掉该节点
tmp.next=tmp.next.next;
}else {
tmp=tmp.next;
}
}
}
public static void main(String[] args) {
CycaLinkList cl = new CycaLinkList<>();
cl.addTail(2);
cl.addTail(3);
cl.print();
System.out.println(cl.getLength());
System.out.println(“索引位0,值为:”+cl.getValueByIndex(0));
System.out.println(“索引位1,值为:”+cl.getValueByIndex(1));
System.out.println(“值2,索引为:”+cl.getIndexByValue(2));
cl.deleteNode(3);
cl.print();
}
}
- 3. 栈和队列
6-1. 队列:FIFO(线性表)只允许一端删除,一端插入;入队和出队;
package datastruct.lianbiao.duilie;
import java.util.Stack;
/**
-
majia
-
19-4-27
-
以一种链式队列实现方式为示例
-
链式队列底层就是单链表
-
循环队列-了解即可
*/
public class LinkQueue {
// 链表的数据结构
private class Node {
public T data;
public Node next;//指针
//构造方法
public Node(){}
public Node(T data, Node next) {
this.data = data;
this.next = next;
}
}//需要有队列的头指针
private Node front;
// 队尾指针
private Node rear;
// 队列队长度
private int size=0;//链表数据对象队构造方法
public LinkQueue(){
front=rear=null;
size=0;
}
/**- 入队,出队,队列长,判断空
*/
//添加元素:入队,元素放到链表尾,从头开始删除
public void push(T value){
Node node = new Node(value, null);
if(rear!=null){//队列不为空
rear.next=node;
}
rear=node;//新节点为队尾
if(frontnull){//插入队式第一个节点
front=node;
}
size++;
}
//出队:删除元素
public T pop() throws Exception {
if(size0){
throw new Exception(“队列已经空了”);
}
Node tmp=front;
front=front.next;
size–;
return tmp.data;
}
//获取队列队头元素
public T getFront(){
if(!empty()){//如果不为空直接返回头节点的值
return front.data;
}else{
return null;
}
}
//判断队列是否为空
public boolean empty(){
return size==0;
}
/**
-
利用队列或者其他数据结构
-
判断字符串是否式回文字符串
-
从前往后从后往前式一样的NBABN
-
思路:利用队列和栈的特性
-
队列:先进先出,先读到的式第一个N
-
栈:先进后出,先读到的式最后一个N
*/
public static boolean isHuiWen(String str) throws Exception {
// 创建队列和栈
Stack stack = new Stack<>();
LinkQueue slq = new LinkQueue<>();
// 循环入栈入队
for (int i = 0; i < str.length(); i++) {
stack.push(str.substring(i,i+1));
slq.push(str.substring(i,i+1));
}
// 迭代的判断
while(!slq.empty()&&!stack.empty()){
if(!slq.pop().equals(stack.pop())){//一旦判断不想等,就不是回文字符
return false;
}
}return true;//是回文字符
}
public static void main(String[] args) throws Exception {
LinkQueue slq = new LinkQueue<>();
slq.push(“a”);
slq.push(“b”);
slq.push(“c”);
System.out.println(“队列的长度:”+slq.size);
System.out.println(“队列是否为空:”+slq.empty());
while (!slq.empty()){
System.out.println(“删除元素:”+slq.pop());
System.out.println(“队列长:”+slq.size);
}String str="NBABc"; if(isHuiWen(str)){ System.out.println(str+"是回文字符串"); }else{ System.out.println(str+"不是回文字符串"); }
}
}
6-1-1. 括号匹配
要检查某一表达式括号是否匹配,可以从左向右扫描表达式的每一个字符,若字符为左右括号,则进行匹配操作:分量中情况: - 入队,出队,队列长,判断空
若是左括号,则将其位置序号进入栈中
若是右括号,则从栈中弹出一个左括号与之匹配,如果栈已空,表示多列一个右括号.
6-1-2. 表达式的中序转后序
中序:(a+b)×(c+d) =》后序ab+cd+×
中序转后序的过程:
使用栈来进行转换,逐个取出中序表达式中的字符,若是运算数,就直接将其输出,若是运算符,则需根据运算符的优先级进行判断:
若是左括号,将其入栈(入栈
若是±*/运算符,用当前运算符与栈顶运算符比较,若栈顶运算符优先级大,则弹出栈顶运算符。接着再将当前运算符与栈顶运算符比较,这样不断循环,直到栈顶运算符的优先级比当前运算符相等或更低时为止,接着将当前运算符入栈。{此时输出a,+比(优先级大入栈,此时栈顶是+输出ab;)比+优先级低,+弹出栈,)比(优先级一样,)入栈此时栈()输出ab+;×优先级比)大入栈此时栈()×,输出ab+;….}
若是右括号,则查看栈顶是否是左括号
6-1-3. 计算阿克曼函数(AckMan)
6-1-4. 栈的输出序列(Gen,Perform)
6-1-5. 递归算法的演示
6-1-5-1. 汉诺塔的算法(Hanoi)
2019-04-27_19-25.png
2019-04-27_19-25.png
public static void main(String[] args) {
hanoi(3,“A”,“B”,“C”);
}
public static void hanoi(int n,String init,String temp,String dest){
if(n==1){
System.out.println("移动"+n+"从"+init+"到"+dest);
}
// 如果移动盘子数量大于一的话
else{
hanoi(n-1,init,dest,temp);//n就是初始化盘子数
moveOne(n,init,dest);
hanoi(n-1,temp,init,dest);
}
}
public static void moveOne(int n,String init,String dest){
System.out.println("移动"+n+"从"+init+"到"+dest);
}
6-1-5-2. 解皇后问题的算法(Queen)
N皇后问题:再N*N的棋盘摆放N个皇后,使得任意两个皇后不在一行,一列或者一斜线上。
回溯法(暴力搜索万金油)本质深度优先(隐式图搜索)时间复杂度O(n^n)(状态空间);
剪枝(确定性):有一个枝访问过了不存在解,可以整枝剪掉(不再访问)。leetcode51题。
package datastruct.juzhen;
import java.util.ArrayList;
import java.util.List;
/**
- majia
- 19-4-28
- 皇后问题
/
public class NHuangHou {//深度优先
public static List<List> ans= new ArrayList<List>();
public static boolean hang[]=new boolean[100];
public static int path[]=new int[100];//记录皇后的位置
public static boolean zhengXieXian[]=new boolean[100];
public static boolean fanXieXian[]=new boolean[100];
public void dfs(int idx,int n){//lie
if(idx>=n){//边界加记录
List chess=new ArrayList();//棋盘
for (int i = 0; i < n; ++i) {
String tmp=“”;
for (int j = 0; j < n; ++j) {
if(j==path[i]){//走的是有皇后的地方
tmp+=“Q”;
}else{
tmp+=“.”;
}
}
chess.add(tmp);
}
ans.add(chess);
return;
}
for (int i = 0; i < n; ++i) {
if(!hang[i]&&!zhengXieXian[idx+i]&&!fanXieXian[idx-i+n-1]) {//判断行,正反斜线是否占了;列用于记录皇后位置了;斜线数量是2×(2n-1),n是行或者列数目
//x一条横,y一条竖线,x+y=k;k是一条斜线
path[idx] = i;
hang[i]=true;
zhengXieXian[idx+i]=true;
fanXieXian[idx-i+n-1]=true;
dfs(idx + 1, n);//执行结束要还原现场
hang[i]=false;//还原现场
zhengXieXian[idx+i]=false;//还原现场
fanXieXian[idx-i+n-1]=false;//还原现场
}
}
}
public List<List> solveNQueens(int n){
ans.clear();
dfs(0,n);
return ans;
}
}
6-1-5-3. 骑士问题
国际象棋棋盘上,有一个骑士从左下角出发,能否不重复的遍历每一个格子?
如何定义关系?
除了剪枝,还有什么办法(求任意解和所有解)
启发式-改变搜索顺序(最快得到任意解;不会改变解得结果),不确定性。
**迭代加深:**限制迭代树得深度每次增加一个深度,得到解只有yes|no即N^P问题优化
求最优->求判定
不需要判重
搜到即是最优
前一个阶段相对下一个阶段仅仅是常数:N7相对N8是常数。
迭代加深+启发式(IDA*)
6-1-5-4. 解迷宫的算法(Maze)
6-1-5-5. 解背包问题的算法(Knap)
6-1-6. 模拟银行(BankSimulation)
6-1-7. 表达式求值(Exp_reduced)
6-2. 串的模式匹配
6-2-1. 古典算法(Index_BF)
6-2-2. 求Next 函数值(Get_next)和按Next 函数值进行匹配 (Index_KMP(next))
6-2-3. 求 Next 修正值(Get_nextval)和按 Next 修正值进行匹配(Index_KMP(nextval))
7. 5. 稀疏矩阵
7-1. 矩阵加法和乘法运算:
2019-04-28_10-06
2019-04-28_10-06
7-2. 矩阵转置 (Trans_Sparmat)
多维矩阵转一维矩阵:
$$
以行为主的下标公式:loc=row*每行元素数量+column
以列为主的下标公式:loc=column*每列的元素数量+row
$$
2019-04-28_10-22.png
7-2-1. 快速矩阵转置 (Fast_Transpos)
7-3. 矩阵乘法 (Multiply_Sparmat)
/**
* 矩阵乘法函数:必须a的行数等于b的列数才能乘法运算
* @param a 矩阵因子a
* @param b 因子矩阵b
* @param c 目标矩阵
* @param x c的行数
* @param y c的列数
* @param z a矩阵的列数
*/
public static void MatrixMul(int[][] a,int[][] b,int[][] c,int x,int y, int z){
int i,j,k;
for ( j = 0; j < y; j++) {//清空目标矩阵
for (i = 0; i <x ; x++) {
c[j][i]=0;
}
}
for (j=0;j<y;j++){//进行矩阵乘法运算
for (i = 0; i < x; i++) {
for (k=0;k<z;k++){
c[j][i]+=a[j][k]*b[k][i];
}
}
}
}
7-4. 广义表
7-4-1. 求广义表的深度(Ls_Depth)
7-4-2. 复制广义表(Ls_Copy)
7-4-3. 创建广义表的存储结构(Crt_Lists)
8. 7. 二叉树
路径:从A节点到B节点的线。从根到任意一个节点有且只有一条路径。一棵树只有一个根
父节点,子节点,叶子节点:没有子节点到节点。每个节点都可以作为一颗子树到根。
层:从根开始到整个节点有多少代
树的每个节点最多只能有两个子节点的树,称为二叉树。两颗子树分别称为左子树,右子树。
8-1. 树和二叉树区别:
树中节点的最大度数没有限制,而二叉树最大度数为2
无序树的节点无左右之分,而二叉树有左右之分,也就是二叉树是有序树。
二叉排序树或者一颗空树:左子树所有节点小于根节点,右子树所有节点大于根节点。左右子树又分别是一个颗二叉排序树。
2019-04-28_09-58
2019-04-28_09-58
8-2. 树的节点类:
/**
- majia
- 19-4-27
- 用于存储树中的节点
- 是节点和二叉树操作分离
*/
public class Node {
// 存储节点数据
private int value;
// 存储该节点的左孩子节点
private Node leftChild;
// 存储该节点的右孩子节点
private Node rightChild;
//构造方法
public Node(int value) {
this.value = value;
}
}
package datastruct.shu;
/**
-
majia
-
19-4-27
-
二叉树类:左节点小于父节点,右节点大于父节点
*/
public class BinaryTree {
// 二叉树 根节点
private Node root = null;//二叉树构造
public BinaryTree(int value) {
root = new Node(value);
root.setLeftChild(null);
root.setRightChild(null);
}/**
- 插入节点
- @param value
- @return
*/
public String insert(int value) {
String result = “”;
// 封装节点数据
Node newNode = new Node(value);
Node current = root;
// 父节点引用
Node parent;
// 判断根节点是否为空
if (root == null) {//根节点为空,树不存在
root = newNode;
return result;
} else {//根节点不为空
while (true) {
parent = current;
if (current.getValue() > value) {//如果插入的值小于当前节点的值,则插入到该节点的左子节点位置
current = current.getLeftChild();
if (null == current) {
parent.setLeftChild(newNode);
return result;
}
} else if (current.getValue() < value) {//如果插入的值大于当前节点的值,插入右子节点位置
current = current.getRightChild();
if (current == null) {
parent.setRightChild(newNode);
return result;
}
} else {//如果插入的值和当前节点的值相等,则提示错误信息。
result = “插入的节点” + value + “已经存在”;
}
}
}
}
/**
- 查询节点
*从根节点开始查找,如果查找的节点值比当前节点的值小,则继续查找其左子树, - 否则查找其右子树
- @param value
- @return
*/
public Node find(int value) {
// 引用当前节点,从根节点开始查找
Node current=root;
// if(current.getValue()value) return current;
while(current.getValue()!=value){
if(current.getValue()>value){//如果查找的节点值比当前节点值小,继续查找左子树
current=current.getLeftChild();
}else if(current.getValue()<value){//如果当前节点值比查找的节点值小,继续查找右子树
current=current.getRightChild();
}
}
// if(currentroot) return null;
return current;
}
/**
- 测试二叉树
- @param args
*/
public static void main(String[] args) {
BinaryTree tree = new BinaryTree(10);
tree.insert(35);
tree.insert(5);
tree.insert(3);
tree.insert(8);
tree.insert(30);
System.out.println(tree.root.getValue());
System.out.println(tree.root.getLeftChild().getValue());
System.out.println(tree.root.getRightChild().getValue());
System.out.println(tree.find(2));
}
}
8-3. (1)遍历二叉树
遍历树是根据一个特定的顺序访问树的每一个节点,根据顺序的不同可以分为先序,中序,后续遍历三种。
先序遍历(Pre_order)
访问根节点
遍历左子树
遍历右子树
中序遍历(In_order)
遍历左子树
遍历根节点
遍历右子树
后序遍历(Post_order)
遍历左子树
遍历右子树
遍历根节点
/**
* 二叉树的前序遍历
* @param node
*/
public void frontOrderNode(Node node){
if(node!=null){
// 访问根节点
System.out.println(“节点:”+node.getValue());
// 遍历左子树
frontOrderNode(node.getLeftChild());
// 遍历右子树
frontOrderNode(node.getRightChild());
}
}
/**
* 二叉树的中序遍历
* @param node
*/
public void midOrderNode(Node node){//3,5,8左子树,30,35右树,10是根
if(node!=null){
// 遍历左子树
midOrderNode(node.getLeftChild());
// 遍历根节点
System.out.println(node.getValue());
// 遍历右子树
midOrderNode(node.getRightChild());
}
}
/**
* 二叉树的后续遍历
* @param node
*/
public void afterOrderNode(Node node){
if(node !=null){
// 遍历左子树
afterOrderNode(node.getLeftChild());
// 遍历右子树
afterOrderNode(node.getRightChild());
// 遍历根节点
System.out.println(node.getValue());
}
}
8-4. 按先序建二叉树(CrtBT_PreOdr)
8-5. 线索二叉树
2019-04-27_23-26.png
2019-04-27_23-26.png
二叉树的线索化
生成先序线索(前驱或后继) (Pre_thre)
中序线索(前驱或后继) (In_thre)
后序线索(前驱或后继) (Post_thre)
遍历中序线索二叉树(Inorder_thlinked)
中序线索树的插入(ins_lchild_inthr)和删除(del_lchild_inthr)结点
(4)建赫夫曼树和求赫夫曼编码(HuffmanCoding)
8-5-1. 最优二叉树(郝夫曼树-带权树)
2019-04-27_23-45.png
2019-04-27_23-45.png
8-6. 森林转化成二叉树(Forest2BT)
8-7. 二叉树转化成森林(BT2Forest)
8-8. 按表达式建树(ExpTree)并求值(CalExpTreeByPostOrderTrav)
9. 8. 图
9-1. 图的定义:有向图,无向图;图的存储:临接矩阵或临接表及代码类体设计。
2019-04-28_08-50.png
2019-04-28_08-50.png
隐式图:点不确定,边不确定的情况下;如何确定点,边?皇后问题,骑士问题案例。
时间复杂度O(n+m)
9-2. 图的遍历:isTrav访问标记的数组
9-2-1. 深度优先搜索(Travel_DFS)
深度遍历类似与树的先序遍历,如下:栈(递归)
从isTrav数组中选择一个未被访问的顶点Vi,将其标记为已被访问
接着从Vi的一个未被访问过的临接点出发进行深度优先遍历
重复步骤2,直至途中所有和Vi有路径相通点顶点都被访问过
重复步骤1-3的操作,直至所有顶点都被访问过。
2019-04-28_11-40.png
//伪代码
void DFS(int v)
visited[v]=true
for(v的每一个临接点w)
if(!visited[w])//如果没有访问过
DFS(w)
9-2-2. 广度优先搜索(Travel_BFS)
广度优先遍历类似与树的按层次遍历,如下:队列,找出的路径经过的点比较少。
从isTrav数组中选择一个未被访问的定点Vi,将其标记为已访问
接着一次访问Vi的所有未被访问的临接点,并标记为已被访问过
从这些临接点出发,进行广度优先遍历,直至图中所有和Vi有路径相通的顶点都被访问过
重复步骤1-3的操作直至所有顶点都被访问过
2019-04-28_12-01.png
void BFS(int x)//伪代码
visited[x]=true
queue.push(x)
while(!Q.empty())
v=queue.pop()
for(v的每个临接点w)
if(!visisted[w])
visited[w]=true
queue.push(w)
9-2-2-1. 种子填充法
FloodFill 洪水填充法
目标:标记某块封闭点区域,并找出其边界
如何定义状态,关系
BFS犹如墨汁滴入清水leetcode200,130
/**
* majia
* 19-4-28
* 洪水填充法--广度优先遍历
*/
public class NumberOfIslands {
public static int qx[]=new int[100000];
public static int qy[]=new int[100000];
// 相当于Pair<x,y>上述两个数组,队列存的数据是元祖
public static int check(int x,int y,char[][] grid,int r,int n,int m){
if(x>=0&&x<n&&y>=0&&y<m&&grid[x][y]=='1'){//扩散点合法性
qx[r]=x;
qy[r]=y;
grid[x][y]='0';//扩散后标为无效,不会再扩散
++r;//入队后指针要偏移
}
return r;
}
public int numIslands(char[][] grid){
if(grid.length==0||grid[0].length==0){
return 0;
}
int n=grid.length;
int m=grid[0].length;
int ans=0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if(grid[i][j]=='1'){
floodFill(i,j,grid,n,m);//用墨水染色,洪水填充
ans++;
}
}
}
return ans;
}
private static void floodFill(int x, int y,char[][] grid,int n,int m) {
int h=0;
int r=1;//[h,r)h,r队头尾的意思
qx[0]=x;
qy[0]=y;
grid[x][y]='0';
while(h<r){//从x,y这个点向四个方向染
r= check(qx[h]-1,qy[h],grid,r,n,m);//探测四个方向,染一个格;这个时候是出队过程
r=check(qx[h],qy[h]-1,grid,r,n,m);//探测四个方向
r=check(qx[h]+1,qy[h],grid,r,n,m);//探测四个方向
r=check(qx[h],qy[h]+1,grid,r,n,m);//探测四个方向
++h;
}
}
}
9-2-2-2. 八数码
3×3的方格内有1-8号方块,求最少的步数,恢复这些方块的顺序?深度优先(暴力法没法达到最优解)还是用广度优先,判重(hash)-解决状态循环造成的状态空间从9!到3^30的问题.
字典树就是一种前缀树,存储省空间
。
八数码优化:
双向搜索重要的是如何判断相遇:hash表。状态空间复杂度
:状态数步数如:38每一步有三个选择状态,经过8步造成的状态空间次数。
双向搜索:起始点和目标点,轮流扩展;hash表判断相遇;复杂度;
启发式:价值函数(启发函数);优先队列(堆);
总结:
9-3. DFS VS BFS:事无绝对
都为暴力搜索,但搜索顺序不同导致实现不同栈|队列得到结果分别可行解|最优解
栈VS队列
可行解VS最优解
递归VS非递归
空间占用,BFS需要存储状态,DFS无需
9-3-1. 求有向图的强连通分量(Strong_comp)
9-3-2. 有向无环图的两个算法
9-3-2-1. 拓扑排序(Toposort)
9-3-2-2. 关键路径(Critical_path)
9-3-3. 求最小生成树
9-3-3-1. 普里姆算法(Prim)
9-3-3-2. 克鲁斯卡尔算法(Kruscal)
9-3-4. 求关节点和重连通分量(Get_artical)
9-3-5. 求最短路径
9-3-5-1. 弗洛伊德算法(shortpath_Floyd)
9-3-5-2. 迪杰斯特拉算法(shortpath_djstl)
10. 9. 存储管理
边界标识法 (Boundary_tag_method)
伙伴系统 (Buddy_system)
紧缩无用单元 (Storage_compaction)
11. 10. 静态查找
查找算法分类:
静态查找和动态查找:静态动态都是针对查找表而言的,动态查找之=指查找表中有删除和插入操作的表
无序查找和有序查找:无序查找指查找数列有序无序均可,有序查找:被查找数列必须为有序数列。
11-1. 顺序查找(Search_Seq)
顺序查找适合存储结构顺序存储或者链式存储的线性表,属于无序查找。性能最差的算法最差情况下O(n)
/**
* 顺序查找算法
* @param arr
* @param key 要查找的元素值
* @return
*/
private static int orderSearch(int[] arr,int key) {
for (int i = 0; i < arr.length; i++) {
if(key==arr[i]){
return i;
}
}
return -1;
}
11-2. 折半查找 (Serch_Bin)(二分查找)
元素必须有序,有序查找的一种。时间复杂度n/(2^m)=>O(log n)
public static void main(String[] args) {
// 定义查找表
int[] arr={1,23,4,5,9,2,11};
// 顺序查找算法
int result=orderSearch(arr,2);
System.out.println(result);
// 二分查找
Arrays.sort(arr);//调用工具类对数组进行排序,默认升序
for (int i: arr) {
System.out.print(i+" ");
}
System.out.println();
// 调用二分查找算法进行查找
System.out.println(binarySearch(arr,2));
System.out.println(binarySearchDigui(arr,2,0,arr.length-1));
}
/**
* 二分查找,非递归实现,定义两个指针分别指向起点和重点
* @param arr
* @param
* @return
*/
private static int binarySearch(int[] arr, int key) {
//定义两个指针
int low=0;
int high=arr.length-1;
// 开始进行循环的判断
while(low<=high){
// 中间点
int mid=(low+high)/2;
if(key==arr[mid]){//找到元素所在点位置
return mid;
}else if(key>arr[mid]){//如果不相等,判断key在那一个区间:在右区间
low=mid+1;
}else {//要查找的值在左区间
high=mid-1;
}
}
// 循环完毕没有找到该值的位置
return -1;
}
/**
* 递归的二分查找法
* @param arr
* @param key
* @param low 查找区间的起点
* @param high 查找区间的终点
* @return
*/
public static int binarySearchDigui(int[] arr,int key,int low,int high){
if(low>high){//没找到
return -1;
}
int mid=(low+high)/2;
if(key==arr[mid]){//找到了
return mid;
}else if(key>arr[mid]){//递归右区间,key在右区间
return binarySearchDigui(arr,key,mid+1,high);
}else{//key在左区间
return binarySearchDigui(arr,key,low,mid-1);
}
}
11-3. 插值查找 (Search_Ins)
11-4. 斐波那契查找 (Search_Fib)
11-5. 次优查找树(BiTree_SOSTree)
12. 11. 动态查找
12-1. 在二叉排序树上进行查找(bstsrch)、插入结点(ins_bstree)和删除结点(del_bstree)
12-2. 在二叉平衡树上插入结点(ins_AVLtree) 和删除结点(del_AVLtree)
12-3. 在 B-树上插入结点(Ins_BTree) 和 删除结点(Del_BTree)
12-4. 在 B+树上插入结点(Ins_PBTree) 和 删除结点(Del_PBTree)
13. 12. 内部排序
13-1. 简单排序法
直接插入排序(Insert_sort)
表插入排序(内含插入(Ins_Tsort) 重排(Arrange)两个算法)
起泡排序(BubbleSort) :思想图和代码
2019-04-25_12-31.png
2019-04-25_12-31.png
public static void main(String[] args) {
int[] arr={1,3,2,4,7,5,9,11};
bianLiShuZu(arr);
// bubbleSort1(arr,arr.length);
bubbleSort0(arr,arr.length);
bianLiShuZu(arr);
}
/**
* 普通冒泡排序
* @param arr
* @param length
*/
private static void bubbleSort0(int[] arr, int length) {
for (int i = 0; i < length; i++) {
for (int j = 0; j < length - i - 1; j++) {
if(arr[j]>arr[j+1]){//前面的元素比后面的大就交换
swap(arr,j,j+1);
System.out.println("第"+(i+1)+"轮,元素"+arr[j]+"和元素"+arr[j+1]+"发生了交换");
}
}
}
}
/**
* 冒泡排序优化算法
* 思路:添加一个标识
* @param arr 带排序的数组
* @param length 数组的长度
*/
private static void bubbleSort1(int[] arr, int length) {
for (int i = 0; i < length; i++) {//外循环控制排序的论次
// 添加一个标识
boolean needSwap=false;
for (int j = 0; j < length - i - 1; j++) {//内存换进行排序交换,-1索引比长度小1;-i是因为排过序的数字不需要重新比较
// 如果发生流元素交换,修改标识的值;每轮比较内层循环找到一个本轮最大值;找到后这轮外层循环就该结束
if(arr[j]>arr[j+1]){
needSwap=true;
swap(arr,j,j+1);
System.out.println(“第”+(i+1)+“轮,元素”+arr[j]+“和元素”+arr[j+1]+“发生了交换”);
}
}
if(!needSwap){
// 退出外层循环
break;
}
}
}
/**
* 对数组元素进行交换位置
* @param arr
* @param j
* @param i
*/
private static void swap(int[] arr, int j, int i) {
// 定义一个临时变量
int tmp=arr[j];
arr[j]=arr[i];
arr[i]=tmp;
}
private static void bianLiShuZu(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
System.out.println();
}
简单选择排序(SelectSort)
2019-04-26_18-35
2019-04-26_18-35
public static void main(String[] args) {
int[] arr={1,9,2,8,4,3,7,6};
System.out.println(“排序前:”);
for (int i : arr) {
System.out.print(i+" “);
}
// 调用排序算法
// selectSort0(arr,arr.length);
selectSort1(arr,arr.length);
System.out.println(“排序后”);
for (int i:arr){
System.out.print(i+” ");
}
}
/**
* 二元选择排序:选择排序优化算法一次迭代选出max,min两个值
* @param arr
* @param length
*/
private static void selectSort1(int[] arr, int length) {
for (int i = 0; i < length/2; i++) {//i的迭代范围从0-length/2]
int min=i;
int max=i;//第i的值得位置不能是length-1
for (int j = i+1; j < length - i; j++) {//j的判断条件去掉最小值和最大值位置
// 比较大值和小值
if(arr[j]<arr[min]){
min=j;
}
//大值比较
if(arr[j]>arr[max]){
max=j;//修改第i轮最大值的位置
}
}
//内层循环结束一轮,就能确定该论循环最小值和最大值
if(min!=i){
sswap(arr,min,i);
System.out.println(“第”+(i+1)+“次迭代,交换最小值,元素”+arr[min]+“和”+arr[i]+“交换”);
}
//如果最大值就在第i位置
if(max==i){
max=min;//把上个判断交换之后的位置赋予给max
}
//还需要确定最大值才行
if(max!=length-1-i){//概论最大值比较的条件,应该在的位置
sswap(arr,max,length-1-i);
System.out.println("第"+(i+1)+"次迭代,交换最大值,元素"+arr[min]+"和"+arr[i]+"交换");
}
}
}
/**
* 定义选择排序大算法
* @param arr
* @param length
*/
private static void selectSort0(int[] arr, int length) {
//外层循环
for (int i = 0; i < length; i++) {
// 定义一个指针变量,用于保存最小值(第i最小值)
int min=i;//最小值位置(默认)
for (int j = i+1; j < length; j++) {
// 元素比较,不是相邻比较,而是当前值和min比较
if(arr[j]<arr[min]){
min=j;//从新记录第i小的元素的位置
}
}
// 比较结束,意味着已经找到最小值(第i小值)所在的位置了
if(min!=i){
sswap(arr,min,i);
System.out.println(“第”+(i+1)+“次迭代,元素”+arr[min]+“和”+arr[i]+“交换”);
}
}
}
/**
* 定义元素交换
* @param arr
* @param min
* @param i
*/
private static void sswap(int[] arr, int min, int i) {
int tmp=arr[min];
arr[min]=arr[i];
arr[i]=tmp;
}
13-2. 复杂排序法
13-3. 堆排序(HeapSort)
堆是一个完全二叉树,树中每个节点对应于原始数据的一个记录
非叶节点的数据大于等于其左右孩子节点的数据(从小到大排序)
若是从大到小排序:非叶节点的数据小于或者等于其左右孩子节点的数据。
堆排序有两个阶段:
将无序堆数据构成堆(即将无序数据生成满足堆定义的完全二叉树)
利用堆排序(即上一步生成堆堆输出有序堆数据):都跟父节点比大小交换位置-最后每次都能最大或最小跑根节点上去。
2019-04-28_09-23.png
13-4. 快速排序(QuickSort)(冒泡的改进)(最优O(nlogn)最坏退化成冒泡o(n^2))
2019-04-26_19-18.png
2019-04-26_19-18.png
package datastruct.neibupaixu;
/**
-
majia
-
19-4-26
-
对数组进行升序排序,使用快排算法
-
思路:想找一个基准元素,然后进行一遍遍历
-
,目的找到基准元素应该在数组中的具体位置
-
定义前后两个指针
-
一个从后往前找,找到比基准元素小的,就调换位置
-
一个从前往后找,找到比基准元素大的就调换位置
-
对前后两组数据分别进行递归
*/
public class KuaiSuPaiXu {
public static void main(String[] args) {
int[] arr={1,9,2,8,7,4,6};
System.out.println(“排序前”);
for (int i : arr) {
System.out.print(i+" “);
}
//进行排序
quickSort0(arr,0,arr.length-1);
System.out.println(“排序后:”);
for (int i : arr) {
System.out.print(i+” ");
}
}/**
*快速排序- @param arr 待排数组
- @param start 起始指针
- @param end 终止指针
*/
private static void quickSort0(int[] arr, int start, int end) {
// 定义基准元素
int key=arr[start];
// 定义偏移元素
int i=start;
int j=end;
// 进行循环
while(i<j){
// 从后往前迭代判断`
while(i<j&&arr[j]>=key){
j–;
}
// 如果找到比基准值小大,就交换
if(i<j){
System.out.println(“找到小值,元素交换–i=”+i+“,j=”+j);
swap(arr,i,j);//把小鱼基准值的元素调换到左边
}
// 从前往后迭代判断
while(i<j&& arr[i]<=key){
i++;
}
//如果找到比基准值大大值就交换
if(i<j){
System.out.println(“找到大值,元素交换–i=”+i+“,j=”+j);
swap(arr,i,j);
}
}
//i>=j,到了这里,就以为值key的位置以经找到轮,i,j重叠轮
System.out.println(“i=”+i+“,j=”+j);
// 开始对左右两边区间元素进行递归迭代
if(i-start>1){//必须保证key的值是可以交换的,key不能再在前两位:即(key=i)不再0,1位置上;终止条件
quickSort0(arr,start,i-1);
}
if(end-j>1){//保证key的值能被交换,不在最后两个位置上:即(key=j)不在arr.length-1,和arr.length-2上
quickSort0(arr,j+1,end);
}
}
/**
- 快排的元素交换
- @param arr
- @param i
- @param j
*/
private static void swap(int[] arr, int i, int j) {
int tmp=arr[i];
arr[i]=arr[j];
arr[j]=tmp;
}
}
13-5. 锦标赛排序(Tournament)
13-6. 其他
13-6-1. 快速地址排序(Qkaddrst)
13-6-2. 基数排序(RadixSort)
- 13. 外部排序
14-1. 多路平衡归并排序(K-Merge)(O(nlogn))
归并排序是建立在归并操作上的一种有效的排序算法,该算法是分治思想的典型应用
把待排序的集合先分割成果敢子集合,然后再对子集合进行排序,待子集合完全有序后,再把所有子集合归并成一个有序的集合.
归并:就是指将果敢个已经排好序的部分合并成一个有序的整体
将两个有序的集合归并成一个有序的集合就称为二路归并.
基本步骤:
1. 判断参数有效性,也就是递归的出口
2. 首先什么也不管,直接把数组平分成两个子数组
3. 递归调用划分数组函数,最后划分到数组只有一个元素,这也意味着数组有序了
4. 然后调用排序归并函数,把亮哥哥有序的数组合并为一个有序的数组
5. 排序函数的步骤,让两个数组元素进行比较把大的/小的元素存放到淋湿数组中,如果有一个数组的元素被取光了,那就直接把另一个数组的元素放到淋湿数组中,最后把淋湿数组中的元素复制到实际数组中.
2019-04-27_08-18.png
2019-04-27_08-18.png
时间复杂度:主要考虑划分数组和合并数组两个函数的时间花销
合并函数复杂度(o(n))代码中有两个长度为n的循环,非嵌套
划分函数:划分位两部分,对分割后的的每一小部分排序花时间为T(n/2),最后把两部分合并位一个有序数组花时间为O(n)
最后的T(n)=O(nlogn)
package datastruct.waibupaixu;
/**
-
majia
-
19-4-27
-
归并排序
-
数据的划分,递归进行
-
数据的归并,排序组合
-
升序示例
*/
public class GuiBingPaiXu {
public static void main(String[] args) {
int[] arr={1,9,2,8,7,4,3,};
System.out.println(“排序前:”);
for (int i : arr) {
System.out.print(i+" “);
}
System.out.println();
// 定义一个归并排序的算法
mergerSort(arr,0,arr.length-1);
System.out.println(“排序后:”);
for (int i : arr) {
System.out.print(i+” ");
}
}/**
*- @param arr 待排序数组
- @param start 开始位置
- @param end 结束位置
*/
private static void mergerSort(int[] arr, int start, int end) {
// 该方法要递归执行,要有出口条件:开始索引比结束索引大,或者数组中只有一个元素
if(start>=end){
return;
}
//平均的切分,找一个中点值
int mid=(start+end)/2;
// 递归待调用 左区间
mergerSort(arr,start,mid);
// 递归待调用 右区间
mergerSort(arr,mid+1,end);
//递归调用到最后,数组都是单元素,就不在递归,就要进行归并-排序-组合
merge(arr,start,mid,end);
}
/**
-
归并 的方法
-
@param arr
-
@param start 起始位置
-
@param mid 中间位置
-
@param end 结束位置
*/
private static void merge(int[] arr, int start, int mid, int end) {
// 需要合并的时候,就可以定义个临时数组,存储值
int[] tmp=new int[arr.length];
int i=start;//左区间探测
int j=mid+1;//这个指针,负责探测第二个区间(右区间)的值,int size=0;//临时数组中的指针
for (;i<=mid&&j<=end;size++) {//i,j都是有区间限定的
if(arr[i]<arr[j]){//左右两个区间的元素(第一次进来两个区间是相同位置)进行比较,哪个值小,先放入tmp临时数组中
tmp[size]=arr[i++];//放入后i自增一,两句合一句
}else{//否则,右区间的值写入临时数组
tmp[size]=arr[j++];
}
}
//循环结束表明至少有一个区间的数据循环比较结束,
// 另外一个区间可能还右数据,没有结束,把这个空间剩余的所有值都存到临时数组中,归并完成
while(i<=mid){//左区间还没结束,有值
tmp[size++]=arr[i++];
}
while(j<=mid){//右区间还没结束,有值,把剩余所有值放入临时数组中
tmp[size++]=arr[j++];
}
// 临时数组是两个区间有序合并后的数据.
// 把tmp中的值再赋给arr
for (i = 0; i < size; i++) {
arr[start+i]=tmp[i];//start从0开始arr也是从0开始
}
}
}
14-2. 置换-选择排序(Repl_Selection)