数据结构预算法基础
数据结构就是研究数据的组织方式
数据结构分为:线性结构:数组,队列,链表,栈
非线性结构:堆,图,树,多维数组,广义表
线性结构意味着数据是一对一的一个元素对应一个数据也就是一个下标对应一个值。
按照存储方式又分为顺序存储,和链式存储。
顺序存储就是在一整块内存中存数据,他们的地址是相连的。
链式存储就是不连续的地址,这样可以充分的利用内存
稀疏数组
当一个数组内部存在过多的数据为默认值0的节点时,在直接进行存储就有些浪费空间了,遇到这钟情况我们可以通过一个小规模的数组来压缩我们所要存储的目标数组。通过这个小规模数组记录全部的非0值的元素。
稀疏数组的第一行数据存储了原始数组的行,列,一共有多少个非0元素
下面的行存储了 原数组第几行,第几列,元素值。
通过这样的方式缩小程序所占内存
应用场景:储存棋盘
队列
队列时线性数据结构中的一种他的特性就是先进先出这和栈刚好相反,队列按照存储方式也有两种实现方式 就是数组和链表。
队列中必有的元素
front指针,rear指针,存储结构(链表或者数组),maxsize
front指向第一个元素的前一个;
rear指向最后一个元素
普通的队列实现
front指针向后走意味着出队
rear指针后走意味着入队
当front==rear时则队空
rear==maxsize则队满
package List;
public class NomorQueue implements Queue {
private int front;
private int rear;
private int MAXSIZE;
private Object[] arr;
public NomorQueue(int i){
arr = new Object[i];
MAXSIZE=i;
front=-1;
rear=-1;
}
@Override
public boolean isFull() {
return rear==MAXSIZE-1;
}
@Override
public boolean isKong() {
return front==rear;
}
@Override
public void add(Object o) {
if (isFull()){
return;
}
arr[rear]=o;
rear++;
}
@Override
public Object remove() {
if (isKong()){
return null;
}
front++;
return arr[front];
}
@Override
public void show() {
if (isKong()){
return;
}
for (int i=front;i<=rear;i++){
System.out.println(arr[i++]);
}
}
}
环形队列实现
普通的队列我们实现之后会发现有一些问题存在,例如当我们存满数组之后在出队这时我们在新插入数据时会判断为队列满,实际上前面的位置是空的。
我们可以通过环形队列来改变数组无法复用的这个问题
首先要实现将队列变为环状的队列那么就要实现我们的front和rear能够回到队列的前面。这就要我们在出队和入队时不能像以前那么去写了。
我们还要能够知道队列的有效元素的个数。因为front和rear能够循环那么判断是否为满也不能直接rearmaxsize了判断为空时依旧可以rearfront。
并且我们会预留一个空的元素节点在数组中用于将rear指向它。
下面我们来用代码实现一下。
package List;
public class ArrQueue implements Queue {
//指向第一个元素
private int front;
//指向最后一个元素的后一个
private int rear;
//队列尺寸
private int MAXSIZE;
//存储结构
private Object[] arr;
public ArrQueue(int i){
arr = new Object[i];
MAXSIZE=i;
front=0;
rear=0;
}
@Override
public boolean isFull() {
//rear在数组中的相对位置如果就是front时那么队列就是空的了
return (rear+1)%MAXSIZE==front;
}
@Override
public boolean isKong() {
return rear==front;
}
@Override
public void add(Object o) {
if (isFull()){
return;
}
arr[rear]=o;
//rear不能直接加这样就不能循环了
rear=(rear+1)%MAXSIZE;
}
@Override
public Object remove() {
if (isKong()){
return null;
}
Object o = arr[front];
front=(front+1)%MAXSIZE;
return o;
}
@Override
public void show() {
if (isKong()){
return;
}
//因为队列是环状的我们不能直接对数组进行遍历
//这边我们要循环的次数应该是有效数据的个数(rear+MAXSIZE-front)%MAXSIZE
for (int i=front;i<front+(rear+MAXSIZE-front)%MAXSIZE;i++){
//但是我们的i现在是可能比下标大的我们应该找到他在队列中的相对位置i%MAXSIZE
System.out.println(arr[i%MAXSIZE]);
}
}
}
链表
程序设计=数据结构+算法
传统上,我们把数据结构分为逻辑结构和物理结构
逻辑结构:
四大逻辑结构:
①集合结构:
②线性结构:
③树形结构:
④图形结构:
物理结构:
顺序存储结构:
链式存储结构:
数据结构和算法的关系:
1.时间复杂度和空间复杂度
事件复杂度=函数体的时间复杂度*执行次数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iq5n73Xi-1582701629850)(F:\opensource\項目積纍文檔\数据结构与算法学习笔记\img\LD@NWP$S4R9_867E03K5]UY.png)]
int i,j;,
for(i=0;i<n,i++){
function(i);
}
void function(int count){
printf("%d",count);
}
计算上面代码的时间复杂度
o(n)
假如function改变为如下的代码
void function(int c){
int j;
for(j=c;j<n;j++){
pringf(j);
}
}
那么他的时间复杂度则变为了O(n^2)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z486kYRZ-1582701629851)(F:\opensource\項目積纍文檔\数据结构与算法学习笔记\img\PU[`5I3M}A~$[2NHZHC8YLE.png)]
2.链表的两种实现方式
像是排队一样具有线一样的性质结构
list(线性表):由零个或多个数据元素组成的有序序列,有前驱和后继除了第一个和最后一个,n表示元素的个数当n为零时为空,允许有空表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dAWBTwhf-1582701629852)(F:\opensource\項目積纍文檔\数据结构与算法学习笔记\img\list1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I5imFXml-1582701629853)(F:\opensource\項目積纍文檔\数据结构与算法学习笔记\img\listAdt.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wYLQFNR8-1582701629853)(F:\opensource\項目積纍文檔\数据结构与算法学习笔记\img\listopetion.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iGt7UhzH-1582701629855)(F:\opensource\項目積纍文檔\数据结构与算法学习笔记\img\llistendadt.png)]
List数据结构的接口如下
public interface list {
//向列表的尾部添加指定的元素
public abstract int add(Object o);
//在列表的指定位置插入指定元素
public abstract void add(int i,Object o);
// 返回列表中指定位置的元素
public abstract Object get(int i);
//返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1
public abstract int indexOf(Object o);
//从此列表中移除第一次出现的指定元素(如果存在)
public abstract boolean remove(int i);
// 用指定元素替换列表中指定位置的元素
public abstract Object set(int i, Object o);
// 如果列表不包含元素,则返回 true
public abstract boolean isEmpty();
//返回列表中的元素数
public abstract int size();
}
线性表的顺序存储结构:在一段连续的内存内存储相同数据量类型的一系列元素,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dAjy2riM-1582701629856)(F:\opensource\項目積纍文檔\数据结构与算法学习笔记\img\arraylist01.png)]
代码 如下:
package List;
import java.util.Arrays;
public class arrayList<T> implements list {
Object[] items;
int size;
public arrayList(){
items = new Object[8];
size=0;
}
//判断长度是否足够
public void isEnouthLength(){
if ((size+1)>items.length){//如果长度不够扩张为原来的1.5倍
items = Arrays.copyOf(items,items.length*3/2);
}
}
@Override
public int add(Object o) {
isEnouthLength();
items[size]=o;
size++;
return size-1;
}
@Override
public void add(int i, Object o) {
if (i>size||i<0){
return;
}
isEnouthLength();
for (int j=size-1;j>=i;j--){//将i位置之后的元素依次后移
items[j+1]=items[j];
}
items[i]=o;
size++;
}
@Override
public Object get(int i) {
if (i<0||i>size||items[i]==null){
return null;
}
return items[i];
}
@Override
public int indexOf(Object o) {
for (int i = 0; i < size; i++) {
if (o.equals(items[i])){
return i;
}
}
return -1;
}
@Override
public boolean remove(int i) {
if (i<0||i>=size){
return false;
}
for (int j=i+1;j>size-1;j++){//将第I个以后的元素依次向前
items[j-1]=items[j];
}
return true;
}
@Override
public Object set(int i, Object o) {
if (i<0||i>=size){
return null;
}
Object _o = items[i];
items[i]=o;
return _o;
}
@Override
public boolean isEmpty() {
return size<=0?true:false;
}
@Override
public int size() {
return size;
}
}
线性表的链式存储结构(单链表):
package List;
public class linkedList implements list {
public class Enity{
Enity next;
Object item;
public Enity(){}
public Enity(Enity next,Object o){
super();
this.next=next;
this.item=o;
}
}
Enity head;//头节点,一般是空节点
int size;//链表尺寸
public linkedList(){
super();
head = null;
size=0;
}
@Override
public int add(Object o) {
Enity enity = new Enity(head,o);
head=enity;
size++;
return 0;
}
@Override
public void add(int i, Object o) {
if (i<0||i>=size)
return;
Enity enity = head;
int j=0;
while (j<i-1){
enity= enity.next;
j++;
}
enity.next=new Enity(enity.next,o);
size++;
}
@Override
public Object get(int i) {
if (i<0||i>=size)
return null;
Enity enity = head;
int j=0;
while (j<i-1){
enity= enity.next;
j++;
}
return enity.item;
}
@Override
public int indexOf(Object o) {
Enity enity = head;
int j=0;
while (j<size&&!o.equals(enity.item)){
enity= enity.next;
j++;
}
return j;
}
@Override
public boolean remove(int i) {
return false;
}
@Override
public Object set(int i, Object o) {
return null;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public int size() {
return 0;
}
}
3.单链表的面试题
1.求单链表中有效节点的个数
2.查找单链表中倒数第K个节点
3.单链表的反转
4.从尾到头打印单链表[方式1:反向遍历,方式2:栈实现]
5.合并两个有序单链表 合并之后依然有序
4.双向链表
5.单项循环链表
6.约瑟夫问题
栈
实际需求:计算器中我们输入一串字符他可以为我们返回相应的结果
计算器输入的表达式,就可以通过栈来计算
栈是一种线性结构,具有先进后出的特性这个和队列正好相反,就像一个大桶先进去的必然会在桶底无法先出来,反而后进去的可以先出来,栈的入和出的操作发生在同一侧就像前面说的水桶一样,栈分为栈顶和栈底栈底是固定的栈顶会随着输入的元素变多而上移。
栈的实现也有两种就像是队列一样,分别可以使用数组和链表实现。下面我们来实现一下
数组实现:
package List;
public class ArrStack implements Stack {
private int MaxSize;
private int top=-1;
private Object[] arr;
public ArrStack(int maxSize){
this.MaxSize=maxSize;
arr = new Object[maxSize];
}
@Override
public boolean isFull() {
return (top+1)==MaxSize;
}
@Override
public boolean isEmpty() {
return top==-1;
}
@Override
public void push(Object o) {
if (isFull()){
return;
}
top++;
arr[top]=o;
}
@Override
public Object pop() {
if (isEmpty()){
return null;
}
Object o=arr[top];
arr[top]=null;
top--;
return o;
}
}
链表实现:
package List;
public class LinkedStack implements Stack {
public class Node{
public Object data;
public Node next;
public Node(Object data,Node next){
this.data=data;
this.next=next;
}
}
private Node head=null;
private int Size;
private Node top=null;
public LinkedStack(){
this.Size=0;
}
@Override
public boolean isFull() {//应为是链表实现所以不会满
return false;
}
@Override
public boolean isEmpty() {
return Size==0;
}
@Override
public void push(Object o) {
Node node = new Node(o,null);
if (top==null){
top=node;
}else {
top.next=node;
top=top.next;
}
Size++;
}
@Override
public Object pop() {
if (isEmpty()){
return null;
}
Object o=top.data;
for (int i=0;i<Size-1;i++){//找到倒数第二个
head=head.next;
}
head.next=null;
Size--;
return null;
}
}
通过自己写的stack来实现之前所说的计算器的需求,这次使用的是数组实现的stack。
思路:
我们的计算器传入一个用空格分隔的字符串( 3 + 4 ) * 5 - 6,这样方便我们用string自带的分割操作来分割字符。
之后我们实现两个栈分别存储操作符和操作数遍历表达式:
1.如果是操作数则直接入数栈
2.如果是操作符则进行如下判断:
1.如果栈为空或者( 则直接压入
2.如果不为空且是 )那么就弹出反复执行 (两个操作数和一个操作符并运算之后的结果压入数栈)直到遇到( 操作符
3.如果不为空不是)那么判断当前操作符优先级小于栈顶优先级就执行 两个操作数和一个操作符并运算之后的结果压入数栈
4.如果不为空 判断当前操作符优先级大于栈顶优先级直接压入
实现代码如下:
public static void main(String[] args) {
Stack stack1 = new ArrStack(10);//操作符栈
Stack stack2 = new ArrStack(10);//操作数栈
String expertion = "( 3 + 4 ) * 5 - 6";//表达式
String[] split = expertion.split(" ");//分割表达式
List<String> list = new ArrayList<>();
for (String s:split) {//将分割后的表达式装入一个list
list.add(s);
}
for (String s:list) {
if (panduanCaozuofu(s)){//是操作符
if (stack1.isEmpty()||"(".equals(s)){//如果栈为空或者( 则直接压入
stack1.push(s);
}else if (")".equals(s)){
//如果不为空且是 )那么就弹出反复执行
// (两个操作数和一个操作符并运算之后的结果压入数栈)直到遇到( 操作符
String ss="";
while (ss.equals("(")){
int x= (int) stack2.pop();
int y= (int) stack2.pop();
String caozuo = (String) stack1.pop();
Integer fin = caozuo(x,y,caozuo);
stack2.push(fin);
ss= (String) stack1.pop();
}
}else if (panduanYouxianji((String) stack1.showTop(),s)){
//如果不为空 判断当前操作符优先级大于栈顶优先级直接压入
stack1.push(s);
}else {
//如果不为空不是)那么判断当前操作符优先级小于栈顶优先级就执行
// 两个操作数和一个操作符并运算之后的结果压入数栈
int x= (int) stack2.pop();
int y= (int) stack2.pop();
String caozuo = (String) stack1.pop();
Integer fin = caozuo(x,y,caozuo);
stack2.push(fin);
}
}else {//是操作数
stack2.push(s);
}
}
Object pop = stack2.pop();
System.out.println(pop);
}
public static boolean panduanCaozuofu(String s){//判断是否为操作符
if ("+".equals(s)){
return true;
}else if ("-".equals(s)){
return true;
}else if ("*".equals(s)){
return true;
}else if ("/".equals(s)){
return true;
}else if(")".equals(s)){
return true;
}else if ("(".equals(s)){
return true;
}else {
return false;
}
}
//判断操作符的优先级,s1为栈顶操作符s2为当前操作符
//返回 ture则代表s2>s1 false表示 s2<=s1
//ture 就是压入 false就是不压入
public static boolean panduanYouxianji(String s1,String s2){
if ("*".equals(s2)||"/".equals(s2)){
if ("*".equals(s1)||"/".equals(s1)){
return false;
}else {
return true;
}
}else {
return false;
}
}
//传入两个数和一个操作符返回操作后的数值
public static int caozuo(int x,int y,String s){
if ("+".equals(s)){
return y+x;
}else if ("-".equals(s)){
return y-x;
}else if ("*".equals(s)){
return y*x;
}else if ("/".equals(s)){
return y/x;
}
return 0;
}
栈的表达式(前缀,中缀,后缀)
前缀表达式也就波兰表达式是我们在计算器中常用的表达式
(3+4)x5-6转换为波兰表达式是:-x+3456
除了中缀表达式其他的都是没有括号的表达式
前缀表达式是从右向左扫描,遇到数字压入栈,遇到符号就弹出两个数字进行操作在压入结果值。
中缀表达式就是我们平常使用的那种
后缀表达式也叫逆波兰表达式,是十分符合计算机扫描和我们的思考逻辑的一中表达式
(3+4)x5-6转换为逆波兰表达式是:34+5x6-
后缀表达式是从左向右扫描遇到数字压入栈,遇到符号弹出两个数字计算之后压入结果值。
将中缀表达式转换为后缀表达式
思路:
1.初始化两个栈,s1,s2
2.扫描表达式,如果是操作数就直接压入S2栈
3.如果是操作符:将优先级与S1栈顶元素优先级比较
1.如果优先级大于S1栈顶元素优先级那么直接压入s1
2.如果优先级小于S1栈顶元素优先级那么就从s1弹出栈顶元素压入s2,在重新回头判断当前元素和新的栈顶元素比较
3.如果是s1为空或者s1栈顶元素是( 则直接压入
4.遇到括号
1.遇到左括号( 直接压入
2.遇到右括号) 反复的执行弹出S1栈顶元素压入S2直到S1栈顶元素是左括号时( 将这一对括号丢弃。
5.重复2到4步骤
6.最后表达式为空了时候,将S1剩下的元素全部压入S2
7.输出S2中的元素 这就是后缀表达式了
递归
递归就是在方法体中调用自己,以此来形成一个循环的调用直到达成指定条件。每次调用自身时都会创建一个新的栈帧,看成一个新创建的方法,他的返回值会回到被调用的地方。每个方法他的局部变量都是独立的,但是引用变量则是共享的。一个递归的判断条件一定是可以达成的 也一定是在不停的接近达成条件的,否则就会形成无限的循环调用,造成内存溢出。
两个具体的问题:小球走迷宫,八皇后问题
小球走迷宫:
用一个二维数组表示迷宫,数组中元素的值表示地形(1:墙,2:走的路,3:走过的路但是回头了,0:没走过的地方)走迷宫要设定一个基本的规则比如说先走下再走上再走左再走右这种。
具体实现:
package suanfa.digui;
public class migong {
int[][] mg;
public int[][] chushihua(){
mg = new int[6][6];
//把墙设置好
for (int i=0;i<mg.length;i++){
mg[i][0]=1;
mg[i][5]=1;
mg[0][i]=1;
mg[5][i]=1;
}
mg[3][1]=1;
mg[3][2]=1;
return mg;
}
/**
* 设置走到mg[4][4]则走出迷宫
* 起始点是mg[1][1]
* 规则是 下 右 上 左
* @param m 地图
* @param i 所在位置的x轴
* @param j 所在位置的y轴
* @return 返回走到最后的路线
*/
public boolean zoumigong(int[][] m,int i,int j){
if (m[4][4]==2){
return true;
}else if (m[i][j]==0){
m[i][j]=2;
if (zoumigong(m,i+1,j)){
return true;
}else if (zoumigong(m,i,j+1)){
return true;
}else if (zoumigong(m,i-1,j)){
return true;
}else if (zoumigong(m,i,j-1)){
return true;
}else {
m[i][j]=3;
return false;}
}else
return false;
}
public static void main(String[] args) {
migong m = new migong();
int[][] chushihua = m.chushihua();
for (int i=0;i<chushihua.length;i++){
for (int j=0;j<chushihua[i].length;j++){
System.out.printf(String.valueOf(chushihua[i][j])+" ");
}
System.out.println();
}
System.out.println("-------------------------");
m.zoumigong(chushihua,1,1);
for (int i=0;i<chushihua.length;i++){
for (int j=0;j<chushihua[i].length;j++){
System.out.printf(String.valueOf(chushihua[i][j])+" ");
}
System.out.println();
}
}
}
八皇后问题:
在一个8X8的棋盘中放置8个棋子使棋子之间任意一行,一列或者是一条斜线上没有两个棋子。问共有多少种排法
基本思路就是:棋盘默认存在 棋子的位置用一个一维数组存储,存储的就是某一行放置了棋子的位置。一行一行的去判断可以不可以。
具体实现:
package suanfa.digui;
public class bahuanghou {
int Max = 8;
int qizi[] = new int[Max];
static int i=0;
//放置第n个棋子
public void check(int n){
if (n==Max){//已经放好8个皇后了
print();
i++;
return;
}
//放入皇后并判断是否有冲突
for (i=0;i<Max;i++){
qizi[n]=i;//第N行放置在第I列
if (panduan(n)){//判断上面放的是否有冲突
check(n+1);//没有冲突放下一个棋子
}
}
}
/**
* 放置第n个皇后时,检测这个皇后是否和前面已经摆放的皇后冲突
* qizi[n] == qizi[i] 判断i行和第n行的棋子是否在同一列
* Math.abs(i - n) == Math.abs(qizi[i] - qizi[n])
* 判断i行和n行之间相隔的行数是否等于i行和n行相差的列数
* @param n 第n个皇后也就是第n行的棋子
* @return
*/
public boolean panduan(int n) {
for (int i = 0; i < n; i++) {
if (qizi[n] == qizi[i] || Math.abs(n - i) == Math.abs(qizi[n] - qizi[i])) {
return false;
}
}
return true;
}
public void print(){
for (int i=0;i<qizi.length;i++){
System.out.print(qizi[i]+" ");
}
System.out.println();
}
public static void main(String[] args) {
bahuanghou bahuanghou = new bahuanghou();
bahuanghou.check(0);
System.out.println(bahuanghou.i);
}
}
排序算法
排序:就是将一组数据按照一定的要求进行顺序的整理
排序算法分为内部排序,和外部排序
内部排序就是一次性将全部的数据拿出来一次排完
外部排序就是由于内存的问题要一次一次的取数据排序再整理到一起
常用的内部排序:
----插入排序 -------------直接插入排序
------------希尔排序
----交换排序 --------------冒泡排序
--------------快速排序
----选择排序 --------------简单选择排序
--------------堆排序
----基数排序(扩展的桶排序)
----归并排序
算法的时间复杂度有事先估计,和事后统计两种
时间频度
平均时间复杂度
最坏时间复杂度
冒泡排序
是一种交换排序算法:按顺序将将相邻的数据进行比较并按要求交换位置循环多次来完成排序。如果一次遍历走下来一次交换都没有发生,则代表排序完成了。
代码实现:
package suanfa.sort;
public class maopaoSort {
public static void main(String[] args) {
//大到小排序
int[] arr = {32,97,12,5,8,11,99,55,90,3};
int sort = sort(arr);
System.out.println(sort);
for (int i:arr) {
System.out.println(i);
}
}
public static int sort(int[] arr){
boolean falg=true;
int n=0;
while (falg){
falg=false;
for (int i = 0; i < 9; i++) {
if (arr[i]<arr[i+1]){
int x = arr[i];
arr[i]=arr[i+1];
arr[i+1]=x;
falg=true;
}
}
n++;
}
return n;
}
}
快速排序
快速排序也是一种交换排序它是对冒泡排序的改进,整体理解就是将一组数据找一个基准,在将全部的数据进行比较比基准小的放在一边比基准大的放在另一边,同理,继续将分开的这两半在进行快速排序,直到无法在分。
这个算法我们可以使用递归的概念来实现
思路:设置被扫描数据序列的最左和最右端在从左到右遍历,一旦找到符合要求的就停下直到两侧都找到符合要求的进行交换。循环上面的操作直到左右相遇如果有多余没有交换的那个就跟基准交换如果没有那就不动。
在将基准两侧的数组进行上述操作
实现代码:
插入排序
插入排序是一种内部排序,通过插入的方式来找到适当的位置。
将表看为无序表和有序表,有序表在最左边只包含一个元素再从无序表中将元素一个一个拿出来插入到有序表中直到排序完毕
我们的代码同样用递归实现
代码实现:
package suanfa.sort;
public class insertSort {
public static void main(String[] args) {
int[] arr = {32,97,12,5,8,11,99,55,90,3};
sort(arr,0);
for (int i:arr) {
System.out.print(i+" ");
}
}
//小到大排序
//x是有序表的最后一个
public static void sort(int[] arr,int x){
if (x==arr.length-1){//一旦走到最后就退出
return;
}
int y=x+1;//y是无需表的第一个
for (int i=0;i<=x;i++){//将无序表的第一个在有序表中比较
if (arr[y]<arr[i]){//符合要求就交换
int n=arr[y];
arr[y]=arr[i];
arr[i]=n;
}
}
sort(arr,x+1);
}
}
希尔排序
希尔排序是对插入排序的改进也叫缩小增量排序,
选择排序
选择排序就是每次遍历数组都找出一个最符合要求的放在相应的位置,循环这个操作直到全部排完
代码实现:
正常循环实现:
public static void sort2(int[] arr){
for(int i =arr.length-1;i>=0;i--){
int max=arr[0];
int x=0;
for (int j=0;j<=i;j++){
if (max<arr[j]){
max=arr[j];
x=j;
}
}
arr[x]=arr[i];
arr[i]=max;
}
}
递归实现
package suanfa.sort;
public class xuanzeSort {
public static void main(String[] args) {
int[] arr = {32,97,12,5,8,11,99,55,90,3};
sort(arr,arr.length);
for (int i:arr) {
System.out.print(i+" ");
}
}
public static void sort(int[] arr,int length){
if (length==1){
return;
}
int max=arr[0];
int x=0;
for (int i=0;i<length;i++) {
if (max<arr[i]){
max=arr[i];
x=i;
}
}
arr[x]=arr[length-1];
arr[length-1]=max;
sort(arr,length-1);
}
}
归并排序
归并排序是用程序中十分广为使用的分治策略的理念来完成的简单地说就是将一个大的整体分为无数个小的部分依次解决在合并为一个整体的过程
代码实现:
基数排序
基数排序也是桶排序的扩展的一种。
简单的理解就是将全部的数据按照个位十位百位的位数的数字放入相应的桶里,最后将桶里的数据依次拿出就是排好序的数据了
代码实现:
package suanfa.sort;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class jishuSort {
public static void main(String[] args) {
int[] arr = {32,97,12,5,8,11,99,55,90,3};
sort(10,arr);
for (int i:arr) {
System.out.print(i+" ");
}
}
public static void rutong(int n,int[] arr){
List<Integer> bucket = new LinkedList<>();
for (int i = 0; i < 10; i++) {//i代表桶的下标
for (int j = 0; j < arr.length; j++) {//遍历数据
if ((arr[j]/n)==i){//判断是否属于当前桶
bucket.add(arr[j]);
}
}
}
for (int i=0;i<bucket.size();i++) {
arr[i]=bucket.get(i);//将数据放回原数组
}
}
public static void sort(int n,int[] arr){
rutong(n,arr);
rutong(n*10,arr);
rutong(n*100,arr);
}
}
查找算法
顺序查找(线性查找)
这是最简单的查找方法,就是遍历并一个一个的比较找到你要的那个返回
package suanfa.search;
public class shunxuSearch {
public static void main(String[] args) {
int[] arr = {32,97,12,5,8,11,99,55,90,3,0};
int search = search(arr);
System.out.println(search);
}
public static int search(int[] arr){
for (int i:arr) {
if (i==0)
return i;
}
return -1;
}
}
二分查找
二分查找是我们十分熟悉的一个查找方法,在小学时候就有用到过,简单地说就是对被查找的数列进行对半分割将要查找的那个与中间值进行比较大的话就在大的那边继续对半找小的话就在小的那边执行一样的操作,二分查找法所面对的对象一定是有序的。
mid=low+(heigh-low)/2
package suanfa.search;
public class erfenSearch {
public static void main(String[] args) {
int[] arr={1,2,3,4,6,8,9,99,998,9999,191985};
search(arr, 0, arr.length-1, 998);
}
public static void search(int[] arr,int low,int height,int key){
int mid=(height+low)/2;
if (height==low){
System.out.println(arr[height]);;
}
if (key>arr[mid]){
search(arr,mid+1,height,key);
}else if (key<arr[mid]){
search(arr,low,mid-1,key);
}else if (key==arr[mid]){
System.out.println(arr[mid]);;
}
}
}
插值查找
插值查找是对二分查找的优化版,他对中间值的定位进行了更深一步的优化以此来减少查找的次数
mid=low+(heigh-low)*(k-a[low])/(a[heigh]-a[low])
package suanfa.search;
public class insertSearch {
public static void main(String[] args) {
int[] arr={1,2,3,4,6,8,9,99,998,9999,191985};
search(arr,0,arr.length-1,998);
}
public static void search(int[] arr,int low,int height,int key){
int mid=low+(height-low)*(key-arr[low])/(arr[height]-arr[low]);
if (height==low){
System.out.println(arr[height]);;
}
if (key>arr[mid]){
search(arr,mid+1,height,key);
}else if (key<arr[mid]){
search(arr,low,mid-1,key);
}else if (key==arr[mid]){
System.out.println(arr[mid]);;
}
}
}
斐波那契查找
斐波那契和上述两种方法是非常像,应为他也是对二分查找的中间值进行了一些改动,这回是找到数列中的黄金分割点
按照斐波那契数列(第一个第二个是1下面的是前面的和)
哈希表
在我们以前写程序时候,会不停的从数据库提取数据,但是如果每一次访问都到数据库中找数据的话,性能难免会被干扰。这样就催生出了缓冲层这个概念,简单的说就是提前将大部分的数据从数据库取出来存在缓冲层,要用到时到缓冲层去找找不到才到数据库中去取。
实现缓冲层可以自己写也可以使用市面上的缓冲产品(如redis,memcahe),自己写的话就可以通过今天所学的这个hash表来写。
哈希表简单的说明就是通过一个数组来储存一系列的链表或者是树之类的存储结构。明显,哈希表可以将数据分开存放在很多个数据结构中,并且通过哈希函数来找到目标数据所在的数据结构,因此哈希表的查询数据会比之前的结构快上不少。存放的数组也叫散列表,而能找到相应数据结构的函数叫做散列函数。
下面代码实现一个哈希表(通过链表实现)
package Hash;
public class HashTabDemo {
public static void main(String[] args) {
LinkedHashTab linkedHashTab = new LinkedHashTab(10);
for (int i = 0; i < 100; i++) {
linkedHashTab.add(i);
}
Node node = linkedHashTab.get(50);
System.out.println(node.getId());
Node node1 = linkedHashTab.get(99);
System.out.println(node1.getId());
}
}
class Node{
int id;
Node next;
public Node(int id,Node next){
this.id=id;
this.next=next;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
class NodeList{
Node head;
Node data;
public NodeList(){
head=null;
}
public void add(int i){
if (head==null){
head=new Node(i,null);
}else {
Node curNode = head;
while (curNode.next!=null){
curNode = curNode.next;
}
curNode.next=new Node(i,null);
}
}
public Node get(int i){
if (head==null){
return null;
}
Node curNode = head;
while (curNode.next!=null){
if (curNode.getId()==i){
break;
}
curNode = curNode.next;
}
return curNode;
}
}
class LinkedHashTab{
NodeList[] arrs;
int size;
public LinkedHashTab(int size){
this.size=size;
arrs = new NodeList[size];
for (int i=0;i<size;i++){
arrs[i] = new NodeList();
}
}
public int getCode(int i){
return i%size;
}
public void add(int i){
int code = getCode(i);
arrs[code].add(i);
}
public Node get(int i){
int code = getCode(i);
return arrs[code].get(i);
}
}
树结构基础
通过上面的一系列学习我们对于数据结构有了不少的了解,那么我们就可以发现上面所有学过的数据结构都可以分为两种,一种是数组实现的还有一种就是链表实现的,而这两种都有他们的优点和缺点而且都有点偏科。
数组实现:例如队列,数组,数组实现的List,数组实现的哈希表,数组实现的栈,这些数据结构都有共同的特点,那就是方便查询应为数组是有下表的不必一个一个去排查,但是数组应为是一个固定的连续的存储空间所以他的增加移除和插入都很不方便。
链表实现:链表实现的数据结构就可以很方便的进行增删改了,应为只需要在最后或者指定的位置调整他的指针节点就可以,但是这个链表的查询有很不方便,应为他没有下标这一说法必须一个一个遍历到你想要的那个数据才行。
而树结构就可以很好的解决这个问题,树结构既方便查询又方便增删。
二叉树有几个基本的概念如下:
完全二叉树:完全二叉树则是满足最后一层的节点可以连起来而且倒数第二层的节点也可以连起来。
满二叉树:满二叉树就是所有的节点都在最后那一层。
遍历二叉树:前序,中序,后序。
前序就是先输出当前节点,在遍历左子树,在遍历右子树
中序就是先遍历左子树,输出当前节点,在遍历右子树
后续就是先遍历左子树,在遍历右子树,输出当前节点
代码实现:
package Tree.base;
public class baseTree {
public static void main(String[] args) {
TreeNode node1 = new TreeNode(1);
TreeNode node2 = new TreeNode(2);
TreeNode node3 = new TreeNode(3);
TreeNode node4 = new TreeNode(4);
TreeNode node5 = new TreeNode(5);
TreeNode node6 = new TreeNode(6);
node1.setLeft(node2);node1.setRight(node3);
node2.setLeft(node4);node2.setRight(node5);
node3.setLeft(node6);
Tree tree = new Tree(node1);
tree.perTravesal();
tree.midTravesal();
tree.subTraversal();
}
}
class TreeNode{
int id;
TreeNode left;
TreeNode right;
public void perTraversal(TreeNode node){
System.out.print(node.getId());
if (node.getLeft()!=null){
perTraversal(node.getLeft());
}
if (node.getRight()!=null){
perTraversal( node.getRight());
}
}
public void midTraversal(TreeNode node){
if (node.getLeft()!=null){
perTraversal(node.getLeft());
}
System.out.print(node.getId());
if (node.getRight()!=null){
perTraversal( node.getRight());
}
}
public void subTraversal(TreeNode node){
if (node.getLeft()!=null){
perTraversal(node.getLeft());
}
if (node.getRight()!=null){
perTraversal( node.getRight());
}
System.out.print(node.getId());
}
public TreeNode(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public TreeNode getLeft() {
return left;
}
public void setLeft(TreeNode left) {
this.left = left;
}
public TreeNode getRight() {
return right;
}
public void setRight(TreeNode right) {
this.right = right;
}
}
class Tree{
TreeNode root;
public Tree(TreeNode root){
this.root=root;
}
public void perTravesal(){
if (root!=null){
root.perTraversal(root);
}
System.out.println();
}
public void midTravesal(){
if (root!=null){
root.midTraversal(root);}
System.out.println();
}
public void subTraversal(){
if (root!=null)
root.subTraversal(root);
System.out.println();
}
}
二叉树的查找也分为前中后序,基本和上面一个概念
代码实现:这里实现一个前序遍历查找
public TreeNode perSearch(TreeNode node,int i){
TreeNode target=null;
if (i==node.getId()){
target=node;
}else {
if (node.getLeft()!=null){
target=perSearch(node.getLeft(),i);
}
if (node.getRight()!=null){
target=perSearch(node.getRight(),i);
}
}
return target;
}
public TreeNode perSearch(int i){
if (root==null){
return null;
}
return root.perSearch(root,i);
}
删除:删除如果目标节点是叶子节点就直接删除,如果是中间节点就将其子树也删除
代码实现:
public void delete(int i){
delete(root,i);
}
private void delete(TreeNode node,int i){
if (root==null){
return;
}
if (i==root.getId()){
root=null;
}else {
if (node.getLeft()!=null){
if (i==node.getLeft().getId()){
node.setLeft(null);
}else {delete(node.getLeft(),i);}
}
if (node.getRight()!=null){
if (i==node.getRight().getId()){
node.setRight(null);
}else {delete(node.getRight(),i);}
}
}
}
顺序存储二叉树:顺序存储二叉树就是通过一个数组来存储二叉树并且可以将这个数组当作树一样去遍历,查找等等操作
顺序存储二叉树主要是针对完全二叉树,第n个对象的左子节点是2Xn+1右子节点是2Xn+2,他的父节点是(n-1)/2
代码实现:
public static void ArrPerTarversal(int[] arr){
ArrPerTarversal(0,arr);
}
public static void ArrPerTarversal(int i,int[] arr){
if (i>=arr.length){
return;
}
System.out.print(arr[i]);
if((2*i+1)<arr.length){
ArrPerTarversal((2*i+1),arr);
}
if ((2*i+2)<arr.length){
ArrPerTarversal((2*i+2),arr);
}
}
线索化二叉树:线索化二叉树的意思就是完全二叉树他的一部分叶子节点或是中间节点有一个或者两个节点没有指向,就是没有利用上的意思,那就将这一部分节点指向他们按照前序遍历或者其他遍历的前驱节点或者后继节点。
);
}
}
return target;
}
public TreeNode perSearch(int i){
if (root==null){
return null;
}
return root.perSearch(root,i);
}
删除:删除如果目标节点是叶子节点就直接删除,如果是中间节点就将其子树也删除
代码实现:
```java
public void delete(int i){
delete(root,i);
}
private void delete(TreeNode node,int i){
if (root==null){
return;
}
if (i==root.getId()){
root=null;
}else {
if (node.getLeft()!=null){
if (i==node.getLeft().getId()){
node.setLeft(null);
}else {delete(node.getLeft(),i);}
}
if (node.getRight()!=null){
if (i==node.getRight().getId()){
node.setRight(null);
}else {delete(node.getRight(),i);}
}
}
}
顺序存储二叉树:顺序存储二叉树就是通过一个数组来存储二叉树并且可以将这个数组当作树一样去遍历,查找等等操作
顺序存储二叉树主要是针对完全二叉树,第n个对象的左子节点是2Xn+1右子节点是2Xn+2,他的父节点是(n-1)/2
代码实现:
public static void ArrPerTarversal(int[] arr){
ArrPerTarversal(0,arr);
}
public static void ArrPerTarversal(int i,int[] arr){
if (i>=arr.length){
return;
}
System.out.print(arr[i]);
if((2*i+1)<arr.length){
ArrPerTarversal((2*i+1),arr);
}
if ((2*i+2)<arr.length){
ArrPerTarversal((2*i+2),arr);
}
}
线索化二叉树:线索化二叉树的意思就是完全二叉树他的一部分叶子节点或是中间节点有一个或者两个节点没有指向,就是没有利用上的意思,那就将这一部分节点指向他们按照前序遍历或者其他遍历的前驱节点或者后继节点。