-------单链表
- 特点:不知道总个数。
package com.weichen.Singlelinkedlist;
import java.util.Stack;
//alt+shift+L
public class Singlelinkedlist2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
//new 新节点
HeroNode_new node1=new HeroNode_new(1, "bbb", "111");
HeroNode_new node2=new HeroNode_new(2, "ccc", "222");
HeroNode_new node3=new HeroNode_new(3, "ddd", "333");
HeroNode_new node4=new HeroNode_new(4, "eee", "444");
HeroNode_new node5=new HeroNode_new(5, "eee", "444");
HeroNode_new node6=new HeroNode_new(6, "eee", "444");
HeroNode_new node7=new HeroNode_new(7, "eee", "444");
HeroNode_new node8=new HeroNode_new(8, "eee", "444");
HeroNode_new head=new HeroNode_new(1, "bbb","bbb");
HeroNode_new updateNode=new HeroNode_new(6, "bbb","-----");
//new 新链表
Linkedlist_new linkedlist=new Linkedlist_new();
// //向链表中顺次添加节点
// System.out.println("在链尾添加节点");
// linkedlist.addLinkedlist(node4);
// linkedlist.addLinkedlist(node2);
// linkedlist.addLinkedlist(node3);
// linkedlist.addLinkedlist(node1);
// //显示链表---顺次
// System.out.println("显示当前链表--顺次添加");
// linkedlist.list();
//向链表中按序添加节点
System.out.println("按序添加节点");
linkedlist.addOrderLinkedlist(node5);
linkedlist.addOrderLinkedlist(node6);
linkedlist.addOrderLinkedlist(node7);
linkedlist.addOrderLinkedlist(node8);
//显示链表--按序
System.out.println("显示当前链表--按序添加");
linkedlist.list();
//链表反转 结果是打印出反转后的链表
System.out.println("链表反转");
linkedlist.reverse(linkedlist.gethHeadNode());
//链表反转
linkedlist.list();
//
System.out.println("链表二次反转");
linkedlist.reverse(linkedlist.gethHeadNode());
//链表反转
linkedlist.list();
//
//反向打印链表
System.out.println("反向打印链表");
linkedlist.reverse_print(linkedlist.gethHeadNode()); //加上头节点为了指针复原
//
//
//链表更改 要更改指定节点的信息,所以要先指定id
System.out.println("更改指定元素");
linkedlist.update(updateNode);
linkedlist.list();
//
//
//删除链表某一指定元素
System.out.println("删除链表某一指定元素");
linkedlist.delete(8);
linkedlist.list();
}
}
//链表
class Linkedlist_new{
private HeroNode_new headNode=new HeroNode_new(0, "","");
public HeroNode_new gethHeadNode() {
return headNode;
}
//添加元素 传入一个节点
public void addLinkedlist(HeroNode_new node4) {
//定义临时指针
HeroNode_new temp=headNode;
//找要添加节点的位置
while(true) {
if(temp.next==null) {
break;
}else {
temp=temp.next;
}
}
//找到位置后放上新节点
temp.next=node4;
}
//按序向链表添加元素
public void addOrderLinkedlist(HeroNode_new b_node) {
HeroNode_new temp=headNode;
boolean flag=false;
while(true) {
if(temp.next==null) { //直接在末尾插入即可
temp.next=b_node;
break;
}
if(temp.next.id==b_node.id) {
System.out.println("重复插入"); //打印一句话退出循环即可
break;
}else if(temp.next.id>b_node.id) { //找到位置
flag=true;
break;
}else {
temp=temp.next; //未找到位置,指针后移
}
}
if(flag) {
b_node.next=temp.next;
temp.next=b_node;
}
}
//遍历链表
public void list() {
int count=0; //用来统计链表中元素个数
HeroNode_new temp=headNode.next;
//遍历链表首先要判断链表是否为空
if(headNode.next==null) {
System.out.println("链表为空");
return;
}
//遍历 若temp!==null则继续遍历 若为空,退出循环
while(true) {
if(temp!=null) {
System.out.println(temp.toString());
count++;
temp=temp.next;
}else {
break;
}
}
}
//返回倒数第k个元素
//首先考虑链表性质,不能提前知道链表元素的个数,所以首先遍历链表得到个数。找到元素位置
public HeroNode_new heroNode_index(HeroNode_new headNode,int index) {
int count=0;
if(headNode.next==null) {
return null;
}
HeroNode_new temp=headNode.next;
while(true) {
if(temp==null) {
break;
}else {
count++;
temp=temp.next;
}
}
//首先要考虑index是否在范围
if(index<=0||index>count) {
return null;
}
//找到需要返回元素的具体位置
int n_index=count-index;
for(int i=0;i<n_index;i++)
{
temp=temp.next;
}
return temp;
}
//更新节点信息 传入一个新节点 先找到节点id
public void update(HeroNode_new updateNode) {
//先判断链表是否为空
if(headNode.next==null) {
System.out.println("链表为空");
return;
}
HeroNode_new temp=headNode.next;
boolean flag=false;
while(true) {
if(temp==null) {
break;
}
if(temp.id!=updateNode.id) {
temp=temp.next;
}else if(temp.id==updateNode.id){
flag=true;
break;
}
}
if(flag){
temp.name=updateNode.name;
temp.nickname=updateNode.nickname;
}
}
//删除指定元素
public void delete(int id) {
if(headNode.next==null) {
System.out.println("链表为空");
return;
}
HeroNode_new temp=headNode;
boolean flag=false;
//先找位置 temp.id=id
while(true) {
if(temp.next.id==id) {
flag=true;
break;
}
if(temp.next.id!=id) {
temp=temp.next;
}
if(temp.next==null) {
System.out.println("没有找到待删除节点");
break;
}
}
if(flag) {
temp.next=temp.next.next;
}
}
//反转链表
public void reverse(HeroNode_new headNode) {
if(headNode.next==null || headNode.next.next==null) {
return;
}
HeroNode_new headnode2=new HeroNode_new(0, "",""); //指向第二个链表
HeroNode_new temp1=headNode.next;
HeroNode_new temp2=headnode2;
HeroNode_new next=null;
while(temp1!=null) {
next=temp1.next;
temp1.next=temp2.next;
temp2.next=temp1;
temp1=next;
}
headNode.next=temp2.next; //这一步很重要
}
//逆向打印链表
public void reverse_print(HeroNode_new headNode) {
//利用栈先进后出的特点,将节点装入栈中,然后从栈中弹出。
Stack<HeroNode_new> stack=new Stack<HeroNode_new>();
HeroNode_new temp=headNode.next;
if(headNode.next==null) {
System.out.println("链表为空");
return;
}
while(temp!=null) {
stack.push(temp);
temp=temp.next;
}
while(stack.size()>0) {
System.out.println(stack.pop());
}
}
}
//节点
class HeroNode_new{
//数据域、指针域
public int id;
public String name;
public String nickname;
public HeroNode_new next;
public HeroNode_new(int id,String name,String nickname) {
this.id=id;
this.name=name;
this.nickname=nickname;
}
public String toString()
{
return "id="+id+" name="+name+" nickname="+nickname;
}
}
一、时间频度:算法的基本操作语句的重复执行次数T(n)
时间复杂度:T(n)的同数量级函数,记作T(n)=o(f(n))。
一. 算法的时间复杂度:
1.常数阶O(1),消耗的时间不随某个变量的增长而增长。
2. 对数阶O(log2(n))
3. 线性阶O(n)
for循环里面的代码会执行n遍,故消耗的时间随着n的变化而变化。
-
线性对数阶O(nlogN)
将时间复杂度为O(logn)的代码循环N遍,则时间复杂度就是n*O(logN),也就是O(nlogN). -
平方阶O(n的平方)
-
平均时间复杂度和最坏时间复杂度
排序算法之堆排序
一. 堆
-
完全二叉树
-
每个结点的值都大于或等于其左右孩子结点的值—大顶堆
特点:arr[i]>=arr[2*i+1]&&arr[i]>=arr[2*i+2] 升序采用大顶堆
-
每个结点的值都小于或等于其左右孩子结点的值—小顶堆
特点:arr[i]<=arr[2*i+1]&&arr[i]<=arr[2*i+2] 降序采用小顶堆
二、堆排序的基本思想:
二叉排序树(Binary Sort Tree):
- BST:对于二叉排序树的任何一个非叶子结点,左子节点的值比当前节点的值小,右子节点比当前节点的值大。
- 示例:
树
一. 数组、链式、树存储方式的分析
-
数组:
a. 优点:可以通过下标直接访问元素,对于有序数组可以使用二分查找提高检索速度。
b. 缺点:若要插入元素,则会整体移动,效率低
总结:访问快,插入慢 -
链式
a. 优点:插入删除比较方便
b. 缺点:检索慢,需要从头节点开始逐个遍历。 -
树:
a. 优势:可提高数据存储和读取的效率。
eg:二叉排序树,可保证数据的检索速度和插入删除修改速度
二、二叉树
- 二叉树示意图:
- 满二叉树示意图:
- 完全二叉树示意图:
三、二叉树的遍历(前序、中序、后序)
- 思路:
类:
节点类(id,name,leftnode,rightnode)
class HeroNode{
private int id;
private String name;
private HeroNode leftHeroNode;
private HeroNode rightHeroNode;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public HeroNode getLeftHeroNode() {
return leftHeroNode;
}
public void setLeftHeroNode(HeroNode leftHeroNode) {
this.leftHeroNode = leftHeroNode;
}
public HeroNode getRightHeroNode() {
return rightHeroNode;
}
public void setRightHeroNode(HeroNode rightHeroNode) {
this.rightHeroNode = rightHeroNode;
}
public HeroNode(int id,String name) {
this.id=id;
this.name=name;
}
new节点
//构建节点
HeroNode root=new HeroNode(1, "qqq");
HeroNode node2=new HeroNode(2, "www");
HeroNode node3=new HeroNode(3, "eee");
HeroNode node4=new HeroNode(4, "rrr");
HeroNode node5=new HeroNode(5, "ttt");
二叉树类(root)
class Binary_Tree{
private HeroNode root; //root节点
public void setRoot(HeroNode root) {
this.root=root;
}
}
new 树:
//构建树
BinaryTree binaryTree=new BinaryTree();
binaryTree.setRoot(root);
root.setLeftHeroNode(node2);
root.setRightHeroNode(node3);
node2.setRightHeroNode(node4);
node3.setLeftHeroNode(node5);
方法:
前序遍历
//在类HeroNode中
public void preOrder() {
//this节点开始进行前序遍历,根、左、右---注意判断(终止)条件
System.out.println(this.toString());
if(this.leftHeroNode!=null) {
this.leftHeroNode.preOrder();
}
if(this.rightHeroNode!=null) {
this.rightHeroNode.preOrder();
}
}
中序遍历
//中序遍历 要先找到最左子节点
public void midOrder() {
//左、根、右
if(this.leftHeroNode!=null) {
this.leftHeroNode.preOrder();
}
System.out.println(this.toString());
if(this.rightHeroNode!=null) {
this.rightHeroNode.preOrder();
}
}
后序遍历
//后序遍历
public void lastOrder() {
//左、右、根
if(this.leftHeroNode!=null) {
this.leftHeroNode.preOrder();
}
if(this.rightHeroNode!=null) {
this.rightHeroNode.preOrder();
}
System.out.println(this.toString());
}
四、二叉树的查找
-
思路:按照前序、中序、后序的方法挨个查找
根据传入的id号进行查找
-
三种查找方式之前序查找
//前序查找
public HeroNode1 preSearch(int id) {
HeroNode1 resultHeroNode=null;
System.out.println("进入前序查找...");
if(this.id==id) {
return this;
}
if(this.leftHeroNode!=null) {
resultHeroNode=this.leftHeroNode.preSearch(id);
}
if(resultHeroNode!=null) {
return resultHeroNode;
}
if(this.rightHeroNode!=null) {
resultHeroNode=this.rightHeroNode.preSearch(id);
}
return resultHeroNode;
}
- 三种查找方式之中序查找
//中序查找
public HeroNode1 midSearch(int id) {
HeroNode1 resultHeroNode=null;
if(this.leftHeroNode!=null) {
resultHeroNode=this.leftHeroNode.midSearch(id);
}
if(resultHeroNode!=null) {
return resultHeroNode;
}
System.out.println("进入中序查找...");
if(this.id==id) {
return this;
}
if(this.rightHeroNode!=null) {
resultHeroNode=this.rightHeroNode.midSearch(id);
}
return resultHeroNode;
}
- 三种查找方式之后序查找
public HeroNode1 lastSearch(int id) {
HeroNode1 resultHeroNode=null;
if(this.leftHeroNode!=null) {
resultHeroNode=this.leftHeroNode.lastSearch(id);
}
if(resultHeroNode!=null) {
return resultHeroNode;
}
if(this.rightHeroNode!=null) {
resultHeroNode=this.rightHeroNode.lastSearch(id);
}
if(resultHeroNode!=null) {
return resultHeroNode;
}
System.out.println("进入后序查找...");
if(this.id==id) {
return this;
}
return resultHeroNode;
}
五、二叉树之删除节点
自平衡二叉(搜索)树之AVL树
平衡二叉树的实现方式:AVL、红黑树、替罪羊树、Treap、伸展树
- 引入原因:对二叉排序树的一个改进。
- 平衡二叉树的特点:
a. 本质上是一颗二叉排序树
b. 一颗空树,或者它的左右两个子树的高度差的高度差的绝对值不超过1。
c. 左右两个子树都是一颗平衡二叉树。
d. 每个节点的平衡因子只可能是1、0、-1
e. 搜索、添加、删除的时间复杂度是O(logn)
- 基本概念:
平衡因子:某节点左子树高度-右子树高度
eg: 7----> 左子树高度:2 右子树高度:4 平衡因子:-2
添加节点不会导致父节点和非祖先节点的失衡,只会导致其祖先节点的失衡。
- 构建平衡二叉树方法:
步骤:
(1)找平衡因子=2的节点
(2)找插入新节点后失去平衡的最小子树
(3)平衡调整
2要素:
(1)距离插入节点最近
(2)平衡因子绝对值>1的结点作为根节点
典型例子1:
典型例子2:
a. 左旋转
条件:rightHeight-leftHeight>1 (右高)
步骤:首先要计算左右子树的高度
示意图:RR(失衡节点和新添加节点之间的大概安息)
b. 右旋转
条件:leftHeight-rightHeight>1 (左高)
步骤:首先要计算左右子树的高度
示意图:LL
右旋转进行调整:
注::该棵子树调整之后和原来的子树高度一致,所以不会导致以上树的失衡。
c. LR--RR左旋转,LL右旋转(双旋)
示意图:LR
d. RL
示意图:
自平衡二叉(搜索)树之红黑树
-
底层数据结构:特殊的二叉查找树
-
产生原因: 避免下述现象产生。
-
红黑树的特点:
a. 每个节点不是黑色就是红色
b. 不会有连在一起的红色节点
c. 根节点都是黑色
d. 每个红色节点的两个子节点都是黑色。叶子节点都是黑色;可以达到相对平衡的状态 -
eg:示意图:
-
变换规则
-
变换示例:
变色::
旋转(左旋):
右旋:
多叉树
- 引入原因:
最坏情况下,磁盘的读写次数等于树的高度。
比较次数虽然相同,但是都是在内存中进行的,这次对于磁盘的读写只用了3次。提高了性能。
面试趴:
一、为什么要用B树:
-
一次磁盘I/O操作会把一个节点的所有内容加载到内存中(这个占据大部分时间),然后在内存中进行查找比较(这个时间几乎可以忽略)。这时,当一个节点的数据量较大时,相应的磁盘I/O操作次数会减少。
-
特点:
允许每个节点可以有更多的数据项和子节点 -
eg: 2-3树
B树结构:
-
特点:
a. 重新组织节点,降低树的高度,减少i/o读写次数提升效率。
b. 将树的度M设置为1024,在600亿个元素中最多只需要4次I/O操作就可读取到想要的元素。
c. 每个索引对应这自己的数据,没有任何冗余。
自己理解:每一个节点存放的数据多,I/O次数降低 -
应用:B树、B+树广泛应用于文件存储系统以及数据库系统中。
-
示意图:
B树之2-3树:
- 特点:由二节点(没有子节点/有两个子节点)和三节点(没有字节点/有三个子节点)构成的树。
- 图示
构建规则:
B+树:
- 特点:
- 示意图:
详情见mysql面试
B*树
查找算法:
面试题之归并排序思路
* 归并排序
* 是一种稳定排序
* 是采用分治法的一个典型应用
* 最佳情况:T(n) = O(n) 最差情况:T(n) = O(nlogn) 平均情况:T(n) = O(nlogn)
* 算法描述:先分后治
* 分:将一整块分成两份 左边:left,mid 右边:mid+1,right
* 合并:(排序+合并) 用一个新的数组,对两边进行排序。
package com.weichen.paixu;
import java.util.Arrays;
==
public class Merge_sort {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] arr= {3,2,5,1,6,5,4,2};
int[] temp=new int[8];
mergesort(arr, 0, arr.length-1, temp);
System.out.println(Arrays.toString(arr));
}
//采用
public static void mergesort(int[] arr,int left,int right,int[] temp) {
if(left<right) {
int mid=(left+right)/2;
//左边
mergesort(arr, left, mid, temp);
//右边
mergesort(arr, mid+1, right, temp);
merge(arr, left, mid, right, temp);
}
}
public static void merge(int[] arr,int left,int mid,int right,int[] temp) {
int i=left;
int j=mid+1;
int t=0;
while(i<=mid && j<=right) {
if(arr[i]<=arr[j]) {
temp[t++]=arr[i++];
}
if(arr[j]<arr[i]) {
temp[t++]=arr[j++];
}
}
while(i<=mid) {
temp[t++]=arr[i++];
}
while(j<=right) {
temp[t++]=arr[j++];
}
//把temp中的值相应的付给arr
int h=0;
int tempLeft=left;
while(tempLeft<=right) {
arr[tempLeft++]=temp[h++];
}
}
}