数组
二分查找法
与log(N)成正比
第s步,等同于log2® 范围r 由2的幂表示范围(2(s))
0 1 2^0 2的0次方
1 2 2^1
2 4 2^2
3 8 2^3
4 16 2^4
5 32 2^5
6 64 2^6
7 128 2^7
无序数组的插入:
常数
无序数组的插入是我们到现在为止所见过的算法中唯一一个与数组中的数据项个数无关的算法。新数据项总是被放在有空的地方,然后增加。无论数组中的数据项个数N有多大,一次插入总是用相同的时间。我i们可以说一个无序数组中插入一个数据项的时间T是一个常数K:
T=K
在现实情况中,插入所需的实际时间与以下这些因素有关:微处理器,编译程序生成程序代码的效率,等等。上面等式中常数K包含了所有这些因素。
线性查找:
与N成正比
在数组数据项中,寻找特定数据项所需的比较次数平均为数据项总数的一半。因此设N为数据项总数,搜索时间T与N的一半成正比:
T= KN/2 ==> T= KN
这个方程说明平均线性查找时间与数组的大小成正比。即如果一个数组增大两倍,则所需花费的查找时间也会相应的增长两倍。
二分查找:
与log(N)成正比
T= K*log2(N)
==> T=K*log(N)
大O表示法
描述线性查找使用了O(N)级时间,二分查找使用了O(logN)级时间。向一个无序数组中的插入使用了O(1),或常数级时间。(小括号中是数字1。)
算法 大O表示法表示时间
线性查找 O(N)
二分查找 O(logN)
无序数组的插入 O(1)
有序数组的插入 O(N)
无序数组的删除 O(N)
有序数组的删除 O(N)
tips:O(1)级时间的算法是最好的,O(logN)次之,O(N)为一般,O(N2)最差。
数组的缺点
无需数组:
插入: O(1)时间 快
查找: O(N)时间 慢
有序数组
查找: O(logN) 快
插入: O(N) 慢
数组被new出来后大小尺寸就被固定了。但通常在开始设计程序时并不会知道会有多少数据项会被放入数组中。
冒泡排序
基本思想:
代码演示
public class Test {
public static void main(String[] args) {
//创建一个新数组
int[] arr = {77,99,44,55,22,88,11,0,66,33};
//打印新数组
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
//进行冒泡排序
for (int i = 0; i < arr.length-1; i++) {
for (int j= 0; j < arr.length-1-i; j++) {
if (arr[j]>arr[j+1]) {
int temp;
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
//打印排序后的数组
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
时间复杂度
选择排序
基本思想:
基本思想:给定数组:int[] arr={里面n个数据};第1趟排序,在待排序数据arr[1]arr[n]中选出最小的数据,将它与arrr[1]交换;第2趟,在待排序数据arr[2]arr[n]中选出最小的数据,将它与r[2]交换;以此类推,第i趟在待排序数据arr[i]~arr[n]中选出最小的数据,将它与r[i]交换,直到全部排序完成。
c) 举例:数组 int[] arr={5,2,8,4,9,1};
第一趟排序: 原始数据:5 2 8 4 9 1
最小数据1,把1放在首位,也就是1和5互换位置,
排序结果:1 2 8 4 9 5
第二趟排序:
第1以外的数据{2 8 4 9 5}进行比较,2最小,
排序结果:1 2 8 4 9 5
第三趟排序:
除1、2以外的数据{8 4 9 5}进行比较,4最小,8和4交换
排序结果:1 2 4 8 9 5
第四趟排序:
除第1、2、4以外的其他数据{8 9 5}进行比较,5最小,8和5交换
排序结果:1 2 4 5 9 8
第五趟排序:
除第1、2、4、5以外的其他数据{9 8}进行比较,8最小,8和9交换
排序结果:1 2 4 5 8 9
代码演示
//选择排序
// 将以下数组进行排序
// [77,99,44,55,22,88,11,0,66,33]
public class Test02 {
public static void main(String[] args) {
//创建一个数组
int[] arr = {77,99,44,55,22,88,11,0,66,33};
//打印数组
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
//进行选择排序
for (int i = 0; i < arr.length-1; i++) {//做第i趟排序
int k = i;
for (int j = i+1; j < arr.length; j++) {//选最小的记录
if (arr[k]>arr[j]) {
k = j;//记录目前找的最小值位置
}
}
//内层循环结束,找到本轮循环最小数值以后,再进行交换
int temp;
temp = arr[i];
arr[i] = arr[k];
arr[k] = temp;
}
System.out.println();
//打印排序后的数组
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
时间复杂度:
简单选择排序的比较次数与序列的初始排序无关。 假设待排序的序列有 N 个元素,则比较次数永远都是N (N - 1) / 2。而移动次数与序列的初始排序有关。当序列正序时,移动次数最少,为 0。当序列反序时,移动次数最多,为3N (N - 1) / 2。
所以,综上,简单排序的时间复杂度为 O(N^2)。
插入排序
基本思想
将一个数据插入到已经排好序的有序数据中
将要排序的是一个乱的数组int[] arrays = {3, 2, 1, 3, 3};
在未知道数组元素的情况下,我们只能把数组的第一个元素作为已经排好序的有序数据,也就是说,把{3}看成是已经排好序的有序数据
一、第一趟排序
用数组的第二个数与第一个数(看成是已有序的数据)比较
如果比第一个数大,那就不管他
如果比第一个数小,将第一个数往后退一步,将第二个数插入第一个数去
二、第二趟排序
用数组的第三个数与已是有序的数据{2,3}(刚才在第一趟排的)比较
如果比2大,那就不管它
如果比2小,那就将2退一个位置,让第三个数和1比较
如果第三个数比1大,那么将第三个数插入到2的位置上
如果第三个数比1小,那么将1后退一步,将第三个数插入到1的位置上
代码演示
//插入排序
// 将以下数组进行排序
// [77,99,44,55,22,88,11,0,66,33]
public class Test03 {
public static void main(String[] args) {
//创建一个数组
int[] arr = {77,99,44,55,22,88,11,0,66,33};
//打印数组
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
//进行插入排序
for (int i = 1; i < arr.length; i++) {//外层循环控制需要排序的趟数,从一开始因为将0看成了有序数据
int temp =arr[i];
//如果前一位(已排序的数据)比当前数据要大,那么就进入循环比较
while (i>=1&&arr[i-1]>temp) {
//让前一位数据往后退一位
arr[i] = arr[i-1];
//不断往前指直到退出循环
i--;
}
arr[i] = temp;
}
System.out.println();
//打印排序后的数组
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
时间复杂度
插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,时间复杂度为O(n^2)。是稳定的排序方法。
对象排序
代码演示
public class Test01 {
public static void main(String[] args) {
Student stu1 = new Student("XiaoMing",18);
Student stu2 = new Student("ZhangSan",18);
Student stu3 = new Student("LiSi",18);
Student stu4 = new Student("WangWu",18);
ArrayList arr = new ArrayList<>();
arr.add(stu1);
arr.add(stu2);
arr.add(stu3);
arr.add(stu4);
//用集合工具类的sort(参数。。。)方法,Comparator自定义排序
Collections.sort(arr, new Comparator() {
@Override
public int compare(Student o1, Student o2) {
return (o1.getName().charAt(0) - o2.getName().charAt(0));
}
});
System.out.println(arr);
}
}
栈(类)
栈只允许访问最后一个数据项,即最后插入的数据项
方法
构造方法摘要
Stack() 创建一个空堆栈。
方法摘要
boolean empty() 测试堆栈是否为空。
E peek() 查看堆栈顶部的对象,但不从堆栈中移除它。
E pop() 移除堆栈顶部的对象,并作为此函数的值返回该对象。
E push(E item) 把项压入堆栈顶部。
int search(Object o) 返回对象在堆栈中的位置,以 1 为基数。
代码演示
public class Stack01 {
public static void main(String[] args) {
Stack st = new Stack<>();
System.out.println(st);//[]
//测试堆栈是否为空。
System.out.println(st.empty());//true
//把项压入堆栈顶部。
st.push(2);
st.push(65);
st.push(25);
System.out.println(st);//[2, 65, 25]
//查看堆栈顶部的对象,但不从堆栈中移除它。
System.out.println(st.peek());//25
//移除堆栈顶部的对象,并作为此函数的值返回该对象
System.out.println(st.pop());//25
//打印已经移除顶部元素后的栈
System.out.println(st);
}
}
时间复杂度
数据项入栈和出栈的时间复杂度为常数O(1)。这也就是说,栈操作所耗的时间不依赖于栈中数据项的个数,因此操作时间很短。栈不需要比较和移动操作。
队列(接口)
第一个被插入的数据项会最先被移除
java.util
接口 Queue
方法
方法摘要 插入与获取队列的元素
boolean1 add(E e) 将指定的元素插入此队列(如果立即可行且不会违反容量限制),在成功时返回 true,如果当前没有可用的空间,则抛出 IllegalStateException。
E2 element() 获取,但是不移除此队列的头。
boolean1 offer(E e) 将指定的元素插入此队列(如果立即可行且不会违反容量限制),当使用有容量限制的队列时,此方法通常要优于 add(E),后者可能无法插入元素,而只是抛出一个异常。
E获取头 peek() 获取但不移除此队列的头;如果此队列为空,则返回 null。
E获取头2 poll() 获取并移除此队列的头,如果此队列为空,则返回 null。
E获取头2 remove() 获取并移除此队列的头。
时间复杂度
队列中插入数据项和一处数据项的时间复杂度为常数O(1)。
双端队列
双端队列就是一个两端都是结尾的队列。队列的每一端都可以插入数据项和移除数据项。(不常用)
优先级队列
优先级队列有一个队头和一个队尾,并且也是从队头移除数据项。不过在优先级队列中,数据项按关键字的值有序,这样关键字最小的数据项(或者在某些实现中是关键字最大的数据项)总是在队头。数据项插入的时候会按照顺序插入到合适的位置以确保队列的顺序。
时间复杂度
插入操作需要O(N)的时间,而删除操作则需要O(1)的时间。
解析算术表达式(难点)
步骤
表达式的转换
算术表达式(中缀表达式)转换成后缀表达式(例)
* 例如:1*(2+3+(5-4))+6
*
* 开始括号:( [ {
* 结束括号:} ] )
*
* 基本思路:
* 1. 在解析的时候,因为如果解析到6的话,已经到结尾了,但程序不知道是否,应该取出栈中存放的运算符号。
* 也可以在遍历结束之后,在取出栈中的运算符,但我选择的是在原字符串基础上加一个结束标志,我使用的是!(叹号)
* 2. 如果是数字就加入到代表后缀表达式代表的字符串中
* 3. 如果是运算符(+ - * /)就都压入栈中
* 4. 如果是大中小开始括号,也同样压入栈中
* 5. 在栈中没有括号的情况下:
* 碰到 加号(+)或者减号( -),就取出栈中所有的运算符,在将+或—压入栈中
* 6. 在栈中有括号的情况下:
* 碰到 加号(+)或者减号( -),取出栈中的运算符,直到碰到开始括号为止(开始括号不取出来!!!),
* 在将当前+或者-压入栈中。
* 7. 在栈中有括号的情况下:
* 碰到结束括号,取出栈中的运算符,直到取出与之配置开始括号为止(开始括号也取出来!!!)。
* 8. 碰到结束标志(!)如果栈中不为空全部取出,结束遍历算数表达式。
*
*
* 示例解析:1*(2+3+(5-4))+6!
* 算数表达式 栈(解析完算数表达式后) 后缀表达式
* 1 1
* 1* * 1
* 1*( *( 1
* 1*(2 *( 12
* 1*(2+(应该执行第5步) *(+ 12
* 1*(2+3 *(+ 123
* 1*(2+3+ *(+ 123+
* 1*(2+3+( *(+( 123+
* 1*(2+3+(5 *(+( 123+5
* 1*(2+3+(5- *(+(- 123+5
* 1*(2+3+(5-4 *(+(- 123+54
* 1*(2+3+(5-4)(应该执行第6步)*(+ 123+54-
* 1*(2+3+(5-4)) * 123+54-+
* 1*(2+3+(5-4))+ + 123+54-+*
* 1*(2+3+(5-4))+6 + 123+54-+*6
* 1*(2+3+(5-4))+6! 123+54-+*6+
*
代码演示
中缀表达式转后缀表达式
public class StackX {
private int maxSize;
private char[] stackArray;
private int top;
public StackX(int maxSize){
this.maxSize=maxSize;
this.stackArray=new char[maxSize];
this.top=-1;
}
public void push(char c){
stackArray[++top]=c;
}
public char pop(){
return stackArray[top--];
}
public char peek() {
return stackArray[top];
}
public int size(){
return top+1;
}
public boolean isEmpty(){
return top==-1;
}
public char peekN(int index){
return stackArray[index];
}
public void displayStack(String s){
System.out.print(s);
System.out.print(" bottom --> top :");
for (int i=0;i
}
class InToPost{
private StackX theStack;
private String in;
private String out="";
public InToPost(String in){
this.in=in;
int stackSize=in.length();
theStack=new StackX(stackSize);
}
public String doTrans(){
for (int i=0; i
计算后缀表达式
class ParsePost{
private StackO theStack;
private String input;
ParsePost(String input){
this.input=input;
}
public int doParse(){
theStack=new StackO(20);
char ch;
int j;
int num1,num2,interAns;
for (j=0;j='0' && ch<='9'){
theStack.push((int)(ch-'0'));
}
else {
num2=theStack.pop();
num1=theStack.pop();
switch (ch){
case '+':
interAns=num1+num2;
break;
case '-':
interAns=num1-num2;
break;
case '*':
interAns=num1*num2;
break;
case '/':
interAns=num1/num2;
break;
default:
interAns=0;
}
theStack.push(interAns);
}
}
interAns=theStack.pop();
return interAns;
}
}
class StackO {
private int maxSize;
private int[] stackArray;
private int top;
public StackO(int maxSize){
this.maxSize=maxSize;
this.stackArray=new int[maxSize];
this.top=-1;
}
public void push(int c){
stackArray[++top]=c;
}
public int pop(){
return stackArray[top--];
}
public int peek() {
return stackArray[top];
}
public int size(){
return top+1;
}
public boolean isEmpty(){
return top==-1;
}
public int peekN(int index){
return stackArray[index];
}
public void displayStack(String s){
System.out.print(s);
System.out.print(" bottom --> top :");
for (int i=0;i
链表
单链表
链表的具体存储表示为:
① 用一组任意的存储单元来存放线性表的结点(这组存储单元既可以是连续的,也可以是不连续的)
② 链表中结点的逻辑次序和物理次序不一定相同。为了能正确表示结点间的逻辑关系,在存储每个结点值的同时,还必须存储指示其后继结点的地址(或位置)信息(称为指针(pointer)或链(link))
链式存储是最常用的存储方式之一,它不仅可用来表示线性表,而且可用来表示各种非线性的数据结构。
结点结构
┌───┬───┐
│data │next │
└───┴───┘
data域–存放结点值的数据域
next域–存放结点的直接后继的地址(位置)的指针域(链域)
链表通过每个结点的链域将线性表的n个结点按其逻辑顺序链接在一起的,每个结点只有一个链域的链表称为单链表(Single Linked List)。
头指针head和终端结点
单链表中每个结点的存储地址是存放在其前趋结点next域中,而开始结点无前趋,故应设头指针head指向开始结点。链表由头指针唯一确定,单链表可以用头指针的名字来命名。
终端结点无后继,故终端结点的指针域为空,即NULL。
双端链表
双端链表不能有助于删除最后一个链接点,因为没有一个引用指向倒数第二个连接点。
链表的效率
在表头插入和删除速度很快,仅需改变一两个引用值,所以花费O(1)的时间。平均下来,查找删除和插入都需要搜索链表中的一半链接点。需要O(N)此比较。
相对于数组链表还是要快一些,因为当插入和删除时,链表不需要移动任何东西。
链表需要多少内存就有多少内存。
有序链表
在有序链表中,数据是关键值有序排列的。有序链表的删除常常是只限于删除在链表头部的最小(或者最大)链结点。不过有时也用find()方法和delete()方法在整个链表中搜索某一特定的点。
链表的效率
在有序链表插入和删除某一项最多需要O(N)比较(平均N/2),因为必须沿着链表上一步一步走才能找到正确的位置。然而,在O(1)的时间内找到或删除最小值,因为他总在表头。
双向链表
每个节点包含对前一个节点的引用,同时有对后一个节点的引用,双向链表允许反向遍历,并可以从表尾删除。
递归
三角数
含 义
能堆成三角形的数总和
定 理
一个奇平方数减1是8个三角数的和
通项式
n(n+1)/2 推导的话和等差求和一样,首项为1.等差是1的和
发现
汉诺塔
汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
假设有n片,移动次数是f(n).显然f(1)=1,f(2)=3,f(3)=7,且f(k+1)=2*f(k)+1。此后不难证明f(n)=2^n-1。
高级排序
希尔排序(shell sort)
基本思想
希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因DL.Shell于1959年提出而得名。希尔排序是记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
目的
我们分割待排序记录的目的是减少待排序记录的个数,并使整个序列向基本有序发展。而如上面这样分完组后,就各自排序的方法达不到我们的要求。因此,我们需要采取跳跃分割的策略:将相距某个“增量”的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。
public static void shellSort(int[] a) {
int d= a.length;//gap的值
while (true){
d = d/ 2;//每次都将gap的值减半
for (int x = 0; x< d; x++) {//对于gap所分的每一个组
for (int i = x+ d; i < a.length; i= i + d) { //进行插入排序
int temp= a[i];
int j;
for (j= i - d; j>= 0 && a[j] > temp;j = j - d){
a[j+ d] = a[j];
}
a[j+ d] = temp;
}
}
if (d== 1) {//gap==1,跳出循环
break;
}
}
}
快速排序(FastSort)
public class FastSort{
public static void main(String[] args) {
System.out.println("Hello World");
int[] a = {12, 20, 5, 16, 15, 1, 30, 45, 23, 9};
int start = 0;
int end = a.length - 1;
sort(a, start, end);
for (int i = 0; i < a.length; i++) {
System.out.print(a[i]+" ");
}
}
public static void sort(int[] a, int low, int high) {
int start = low;
int end = high;
int key = a[low];
while (end > start) {
//从后往前比较
while (end > start && a[end] >= key) //如果没有比关键值小的,比较下一个,直到有比关键值小的交换位置,然后又从前往后比较
end--;
if (a[end] <= key) {
int temp = a[end];
a[end] = a[start];
a[start] = temp;
}
//从前往后比较
while (end > start && a[start] <= key)//如果没有比关键值大的,比较下一个,直到有比关键值大的交换位置
start++;
if (a[start] >= key) {
int temp = a[start];
a[start] = a[end];
a[end] = temp;
}
//此时第一次循环比较结束,关键值的位置已经确定了。左边的值都比关键值小,右边的值都比关键值大,但是两边的顺序还有可能是不一样的,进行下面的递归调用
}
//递归
if (start > low) sort(a, low, start - 1);//左边序列。第一个索引位置到关键值索引-1
if (end < high) sort(a, end + 1, high);//右边序列。从关键值索引+1到最后一个
}
}
桶排序(bucketSort)
有局限性(可以用基数排序解决)
public static void main(String[] args) {
int[] data = new int[] {26,53,67,30,57,13,48,32,60,50};
int[] s = sort(data,67);
for (int i = 0; i < s.length; i++) {
if (s[i]!=0) {
System.out.print(s[i]+" ");
}
}
}
public static int[] sort(int[] a,int maxNum) {
int[] arr = new int[maxNum+1];
for (int i = 0; i < a.length; i++) {
arr[a[i]] = a[i];
}
return arr;
}
基数排序(radixSort)
public static void main(String[] args) {
int[] A=new int[]{73,22, 93, 43, 55, 14, 28, 65, 39, 81};
radixSort(A, 100);
for(int num:A)
{
System.out.print(num+" ");
}
}
private static void radixSort(int[] array,int d) {
int n=1;//代表位数对应的数:1,10,100...
int k=0;//保存每一位排序后的结果用于下一位的排序输入
int length=array.length;
int[][] bucket=new int[10][length];//排序桶用于保存每次排序后的结果,这一位上排序结果相同的数字放在同一个桶里
int[] order=new int[length];//用于保存每个桶里有多少个数字
while(n
归并排序(mergeSort)
public static void main(String[] args) {
int[] data = new int[] { 2, 4, 7, 5, 8, 1, 3, 6 };
System.out.print("初始化:\t");
print(data);
System.out.println("");
mergeSort(data, 0, data.length - 1);
System.out.print("\n排序后: \t");
print(data);
}
public static void mergeSort(int[] data, int left, int right) {
if (left >= right)
return;
int center = (left + right) / 2;
mergeSort(data, left, center);
mergeSort(data, center + 1, right);
merge(data, left, center, center + 1, right);
System.out.print("排序中:\t");
print(data);
}
public static void merge(int[] data, int leftStart, int leftEnd,
int rightStart, int rightEnd) {
int i = leftStart;
int j = rightStart;
int k = 0;
int[] temp = new int[rightEnd - leftStart + 1]; //创建一个临时的数组来存放临时排序的数组
while (i <= leftEnd && j <= rightEnd) {
if (data[i] > data[j]) {
temp[k++] = data[j++];
} else {
temp[k++] = data[i++];
}
}
while (i <= leftEnd) {
temp[k++] = data[i++];
}
while (j <= rightEnd) {
temp[k++] = data[j++];
}
k = leftStart;
for (int element : temp) {
data[k++] = element;
}
}
public static void print(int[] data) {
for (int i = 0; i < data.length; i++) {
System.out.print(data[i] + "\t");
}
System.out.println();
}
正三角
//正三角
public class demo {
public static void main(String[] args) {
for (int i=0;i<5;i++){
for (int m=0;m<5-i;m++){
System.out.print(" ");
}
for (int n=0;n<=i;n++){
System.out.print("+ ");
}
System.out.println();
}
}
}
字符串反转
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入字符串 按回车键结束");
char[] inputChars = sc.next().toCharArray();
for (int i = inputChars.length-1; i >=0 ; i--) {
System.out.print(inputChars[i]);
}
System.out.println();
}