顺序表
顺序表分为两种:
- 静态开辟:用定长数组来存储
- 动态开辟:使用动态开辟的数组开辟
顺序表不是单纯的数组,他还需要一个变量capacity用来储存顺序表中的有效数据个数:
本博客主要粘贴在java中实现顺序表的接口,如需思路看如下博客:数据结构(c语言)-2-CSDN博客
指定位置插入元素
//判满函数
//判断这个数组是否满了的方法
private boolean isFull()
{
//判断此时数组容量和有效数据是否相等,相等就代表这个数组已经满了
//方法一:
if(this.usedSize == this.elem.length)
{
return true;
}
return false;
//方法二
// return this.usedSize == this.elem.length;
}
// 在 pos 位置插入元素
public void add(int pos, int data)
{
// 判断数组是否满
if(isFull())
{
//如果满了就扩容
this.elem =
Arrays.copyOf(this.elem, 2 * this.elem.length);
}
if(pos < 0 || pos > this.usedSize)
{
return;
}
for(int i = this.usedSize - 1; i >= pos;i--)
{
this.elem[i + 1] = this.elem[i];
}
this.elem[pos] = data;
this.usedSize++;
}
查找顺序表中是否有某个数,和 查找某个数的下标
// 判定是否包含某个元素
public boolean contains(int toFind) {
for(int i = 0;i < this.usedSize;i++)
{
if(this.elem[i] == toFind)
{
return true;
}
}
return false;
}
// 查找某个元素对应的位置
public int search(int toFind) {
for(int i = 0; i < this.usedSize;i++)
{
if(this.elem[i] == toFind)
{
return i;
}
}
return -1;
}
传回pos位置对应数据的值
//判断顺序表里面有没有元素
private boolean isEmpty(){
return this.usedSize == 0;
}
//判断pos位置是否合法
private void checkPos(int pos)
{
if(pos < 0 || pos >= this.usedSize)
{
throw new RuntimeException("pos 不合法");
}
}
// 获取 pos 位置的元素
public int getPos(int pos) {
//判断pos的合法性
checkPos(pos);
//判断顺序表是否是空
if(isEmpty())
{
System.out.println("顺序表为空,请插入数据之后再查找");
//手动抛出异常
throw new RuntimeException("顺序表为空,请插入数据之后再查找");
}
return this.elem[pos];
}
删除第一次出现的key的值
//删除第一次出现的关键字key
public void remove(int toRemove) {
//判空
int index = search(toRemove);
if (index == -1) {
System.out.println("没有你要删除的数据");
return;
}
for (int i = index; i < this.usedSize - 1; i++) {
this.elem[i] = this.elem[i + 1];
}
this.usedSize--;
}
整个顺序表代码
import sun.awt.geom.AreaOp;
import java.util.Arrays;
/**
* @Author liwenyan
* @Date 2023/3/18 12:00
* @PackageName:PACKAGE_NAME
* @ClassName: MyArrayList
* @Description: TODO
* @Version 1.0
*/
public class MyArrayList {
public int[] elem;//数组,此时只是一种类型,还没有分配空间,没有初始化
public int usedSize = 0;//有效的数据个数
public static final int intCapacity = 10;//初始容量
public MyArrayList() {
this.elem = new int[intCapacity];//数组的初始化
this.usedSize = 0; // 可以不初始化为0,因为这个变量是成员
}
//判断pos位置是否合法
private void checkPos(int pos) {
if (pos < 0 || pos >= this.usedSize) {
throw new RuntimeException("pos 不合法");
}
}
//判断这个数组是否满了的方法
private boolean isFull() {
//判断此时数组容量和有效数据是否相等,相等就代表这个数组已经满了
//方法一:
if (this.usedSize == this.elem.length) {
return true;
}
return false;
//方法二
// return this.usedSize == this.elem.length;
}
//实现顺序表的接口
// 打印顺序表
public void display() {
// System.out.println(Arrays.toString(this.elem));
for (int i = 0; i < this.usedSize; i++) {
System.out.print(this.elem[i] + " ");
}
System.out.println();
}
// 在 pos 位置新增元素
public void add(int pos, int data) {
if (isFull()) {
//如果满了就扩容
this.elem =
Arrays.copyOf(this.elem, 2 * this.elem.length);
}
if (pos < 0 || pos > this.usedSize) {
return;
}
for (int i = this.usedSize - 1; i >= pos; i--) {
this.elem[i + 1] = this.elem[i];
}
this.elem[pos] = data;
this.usedSize++;
}
// 判定是否包含某个元素
public boolean contains(int toFind) {
for (int i = 0; i < this.usedSize; i++) {
if (this.elem[i] == toFind) {
return true;
}
}
return false;
}
// 查找某个元素对应的位置
public int search(int toFind) {
for (int i = 0; i < this.usedSize; i++) {
if (this.elem[i] == toFind) {
return i;
}
}
return -1;
}
// 获取 pos 位置的元素
public int getPos(int pos) {
//判断pos的合法性
checkPos(pos);
//判断顺序表是否是空
if (isEmpty()) {
System.out.println("顺序表为空,请插入数据之后再查找");
//手动抛出异常
throw new RuntimeException("顺序表为空,请插入数据之后再查找");
}
return this.elem[pos];
}
// 给 pos 位置的元素设为 value
public void setPos(int pos, int value) {
checkPos(pos);
this.elem[pos] = value;
}
//删除第一次出现的关键字key
public void remove(int toRemove) {
//判空
int index = search(toRemove);
if (index == -1) {
System.out.println("没有你要删除的数据");
return;
}
for (int i = index; i < this.usedSize - 1; i++) {
this.elem[i] = this.elem[i + 1];
}
this.usedSize--;
}
//判断顺序表里面有没有元素
private boolean isEmpty() {
return this.usedSize == 0;
}
// 获取顺序表长度
public int size() {
return this.usedSize;
}
// 清空顺序表
public void clear() {
this.usedSize = 0;
}
public static void main(String[] args) {
//当我们以后要调用顺序表的时候,就调用这个类就行了
MyArrayList myArrayList1 = new MyArrayList();
//顺序插入1 - 10 的数据再顺序表里
for (int i = 0; i < 10; i++) {
myArrayList1.add(i, i);
}
//打印顺序表 此时只有 1 - 10 的元素
myArrayList1.display();
//在尾部插入一个 112 ,此时插入需要扩容
myArrayList1.add(10, 112);
//打印插入之后的顺序表
myArrayList1.display();
System.out.println("============");
//查找数据和查找下标函数测试
System.out.println(myArrayList1.search(5));//5
System.out.println(myArrayList1.search(234));//-1
System.out.println(myArrayList1.contains(5));//true
System.out.println(myArrayList1.contains(15));//false
System.out.println("============");
System.out.println(myArrayList1.getPos(4));//4
myArrayList1.display();
//0 1 2 3 4 5 6 7 8 9 112
System.out.println("============");
myArrayList1.remove(0);
myArrayList1.display();
//1 2 3 4 5 6 7 8 9 112
System.out.println("============");
System.out.println("clear()");
myArrayList1.clear();
myArrayList1.display();
// 没有打印数据
}
}
汉诺塔问题
汉诺塔问题是一个经典的问题。汉诺塔(Hanoi Tower),又称河内塔,源于印度一个古老传说。
大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。
大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。
并且规定,任何时候,在小圆盘上都不能放大圆盘,且在三根柱子之间一次只能移动一个圆盘。
问应该如何操作?
//汉诺塔问题
//1 A -> C 2^1-1 = 1
//2 A -> B A -> C 2^2-1 = 3
//3 A -> C A -> B C -> B A -> C B -> A B -> C A -> C = 7
//64 2^64 - 1
不管有多少个圆盘,我们要达到目的就得先把上面的 n-1 个圆盘同个目的地位置,挪到中途位置:
也就是说,我们要想办法通过C来把上面的n-1个圆盘都挪到中间位置(B)。
挪动之后如下图所示:
然后再把最大的那个圆盘,也就是现在起始位置(A)所剩的圆盘,诺到C上,然后再通过起始位置(A)把中途位置(B)上的n-1个圆盘都挪动到C上。
代码实现:
public class textdome {
//代替鼠标在屏幕上打印出结果
public static void move(char pos1, char pos2)
{
System.out.println(pos1 + "->" + pos2 + " ");
}
/*
* n 盘子个数
* pos1 起始位置
* pos2 中途位置
* pos3 目的地位置
* */
public static void hanoi(int n ,char pos1,char pos2,char pos3)
{
if(n == 1)
{
move(pos1,pos3);
}
else
{
hanoi(n-1,pos1,pos3,pos2);
move(pos1,pos3);
hanoi(n-1,pos2,pos1,pos3);
}
}
public static void main(String[] args) {
hanoi(1,'A','B','C');
System.out.println("=========");
hanoi(2,'A','B','C');
System.out.println("=========");
hanoi(3,'A','B','C');
}
}
青蛙跳台阶问题
一只青蛙一次可以跳上 1 级台阶,也可以跳上2 级。求该青蛙跳上一个n 级的台阶总共有多少种跳法
其实就是斐波那契数列的变性:
public class textdome{
public int JumpFloor (int target)
{
if(target == 1)
{
return 1;
}
else if(target == 2)
{
return 2;
}
else
{
return JumpFloor(target - 1) + JumpFloor(target - 2);
}
}
public static void main(String[] agrs)
{
int n = 6;
textdome textdome1 = new textdome();
int times = textdome1.JumpFloor(n);
System.out.println(times);
}
}
初识 JVM 内存区域划分
- 程序计数器 (PC Register): 只是一个很小的空间, 保存下一条执行的指令的地址.
- 虚拟机栈(JVM Stack): 重点是存储局部变量表(当然也有其他信息). 我们刚才创建的 int[] arr 这样的存储地址的引用就是在这里保存.
- 本地方法栈(Native Method Stack): 本地方法栈与虚拟机栈的作用类似. 只不过保存的内容是Native方法的局部变量. 在有些版本的 JVM 实现中(例如HotSpot), 本地方法栈和虚拟机栈是一起的.
- 堆(Heap): JVM所管理的最大内存区域. 使用 new 创建的对象都是在堆上保存 (例如前面的 new int[]{1, 2,3} )
- 方法区(Method Area): 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据. 方法编译出的的字节码就是保存在这个区域.
- 运行时常量池(Runtime Constant Pool): 是方法区的一部分, 存放字面量(字符串常量)与符号引用. (注意 从 JDK1.7 开始, 运行时常量池在堆上).
数组
存放相同数据类型的集合,内存是连续的,同C语言不一样,java的数组存放在堆上,而C中的数组存放在栈上,而栈比堆小得多,所以在C中我们严格在创建数组的时候严格定义了数组的长度,也就是静态开辟,而如果需要动态开辟空间,在C中就需要malloc , realloc这种函数来实现动态开辟空间,而java因为数组是储存在堆上所以,有动态数组的定义方式,不再像C中一样麻烦。
基本语法
我们在java中定义数组个数时不能指定大小,当我们在初始化数组的时候,我们定义的数据个数,java编译器会自动的给我计算出数组的大小。如果我们在初始化时初始化了数组的元素,那我们在再 " [] " 中指定了大小,java反而会报错。
动态初始化:
public static void main(String[] agrs)
{
int[] array2 = new int[4]{11,22,33,44,55};
}
当然我们也可以在初始化的时候,不给定数组的元素的值,只给定数组的大小,而且此处初始化定义java会给我们自动的初始化为 0,而C中数组不初始化,数组中是随机值。
int[] array3 = new int[4];
for(int i = 0;i < 4;i++)
{
System.out.print(array3[i]); // 0000
}
我们上述定义数组中使用的new ,这个new的作用是把一个对象实例化:
其中的array2 是一个变量名,这个变量的类型是 int [] 。
那么我们上述的三种数组的定义方式,在内存中是如何分布,各自又有上面用呢?
int[] array = {1,2,3,4,5,6,7,8,9,10};
int[] array2 = new int[]{11,22,33,44,55};
int[] array3 = new int[4];
我们不管在定义数组的时候使没使用new,数组都会在堆上建立。
我们的array2既然是一个变量,那么这个array2就是在栈上存储的,而后面的一串是在堆上存储的。
这里的array2是一个引用类型,存放的是其对应在堆上开辟的空间的地址:
虽然此处类似于C中的指针运用,但是我们不能拿到某个变量的地址,也就是说,java是一个很安全的语言,我们不能随便的取拿到某个变量,对象等等这些的地址,我们拿不到栈上的地址。
所谓引用其实就是一个指针。
System.out.println(array2);
既然array2上储存的是对中数组对象的地址,那么我们打印一下这个array2,这边变量会输出上面呢?
我们发现屏幕上打印了这个结果,虽然我们看着这个像是一个乱码,其实这个就是地址,因为这个地址是java经过处理之后的结果,这个处理叫做(哈希)。
也就是我们拿不到栈上面的地址,但是可以拿到堆上的地址,但是这个地址经过哈希处理的。
求数组长度:
以下是静态初始化:
public static void main(String[] agrs)
{
int[] array = {1,2,3,4,5,6,7,8,9,10};
int len = array.length;
System.out.println(len); //10
}
需要注意的是:静态初始化的时候,数组元素个数和数据的格式是一样的。
遍历数组:
for循环遍历
public static void main(String[] agrs)
{
int[] array = {1,2,3,4,5,6,7,8,9,10};
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");//1 2 3 4 5 6 7 8 9 10
}
}
foreach 访问数组:
public static void main(String[] agrs)
{
int[] array = {1,2,3,4,5,6,7,8,9,10};
for (int val:array) {
System.out.print(val); //12345678910
}
}
此时我只需要打印这个val 就可以打印整个数组的内容。
我们发现用for循环看来打印的话是利用索引来打印的,而foreach只用了一个变量,所以以后如果只需要简单遍历一下数组就可以使用foreach这个方式来遍历数组,如果需要一些操作还是for循环比较方便,比如修改某下标的元素,那么我们只能用索引来实现,也就只能使用for循环。
数组访问越界
public static void main(String[] agrs)
{
int[] array = {1,2,3,4,5,6,7,8,9,10};
System.out.print(array[100]);//
}
数组在方法中传参
public class textdome{
public static void printArray(int[] array)
{
for (int val:array)
{
System.out.print(val + " ");//1 2 3 4 5 6 7 8 9 10
}
}
public static void main(String[] agrs)
{
int[] array = {1,2,3,4,5,6,7,8,9,10};
printArray(array);
System.out.print(array[4]);
}
}
public class textdome{
public static void swap(int[] array)
{
int tmp = array[0];
array[0] = array[1];
array[1] = tmp;
}
public static void printArray(int[] array)
{
for (int val:array)
{
System.out.print(val + " ");//1 2 3 4 5 6 7 8 9 10
}
}
public static void main(String[] agrs)
{
int[] array = {10,20};
swap(array);
printArray(array); //20 10
}
}
我们也可以在函数中修改数组中的内容。
利用Array 中的 toString 把数组中的内容以字符串的形式来输出:
public static void main(String[] agrs)
{
int[] array = {1,2,3,4,5};
String inout = Arrays.toString(array);
System.out.print(inout);//[1, 2, 3, 4, 5]
}
数组作为数组的返回值
在C中数组不能作为函数的返回值,但是在java中,数组可以作为方法的返回值。
public class textdome{
public static int[] func(int[] array)
{
int[] tmparray = new int[array.length];
for(int i = 0; i < array.length;i++)
{
tmparray[i] = array[i] * 2;
}
return tmparray;
}
public static void main(String[] agrs)
{
int[] array = {1,2,3,4,5};
int[] array_2 = func(array);
System.out.print(Arrays.toString(array));//[1, 2, 3, 4, 5]
System.out.println();
System.out.print(Arrays.toString(array_2));//[2, 4, 6, 8, 10]
}
}
之前说过 int[] 是一个类型那么,我们就可以直接用这个类型作为方法的返回值,来写直接return方法中的数组
Arrays中的sort排序方法:
public static void main(String[] agrs)
{
int[] array = {5,4,3,2,1};
int[] array_2 = func(array);
Arrays.sort(array);
for (int val:array) {
System.out.print(val);//12345
}
}
顺序排序数组中的数据。
实现MytoString
首先我们看到上图是数组元素,下图是toString打印的结果,我们只需要把每一个元素的用字符串的形式在屏幕上打印出来就行了,需要注意的是,在打印的开始和结尾打上中括号,和在打印每一个数据之前都用一个字符 “ ,” 分隔开来。
代码实现:
public class textdome{
public static String MytoSting(int[] array)
{
String ret = "[";
for(int i = 0;i < array.length;i++)
{
ret += array[i];
if(i != array.length - 1)
{
ret += ",";
}
}
ret += "]";
return ret;
}
public static void main(String[] agrs)
{
int[] array = {5,4,3,2,1};
System.out.print(MytoSting(array));//[5,4,3,2,1]
}
}
其实拼接字符串用 “ + ” 不太好,我们拼接字符串一般用这两个方法:
null
在C中我们如果在创建数组的时候还不知道给这个数组赋上上面值,那么我们可以这样做:
int arr[] = {};
但是我们在java中不能这么做,这样做如果创建的是一个局部变量,那么就会报错。但是我们可以使用null来表示这个array 此时引用的是一个空对象。
int[] array = null;
java中的null 和C中的NULL不一样,NULL一般赋值给指针,代表这个指针是一个空指针,NULL代表的0地址处,但是java中的null不是0地址,null就是一个空对象。
但是就算我们给他初始化为null了也不能对这个array进行访问:
public static void main(String[] agrs)
{
int[] array = null;
System.out.print(array[0]);
}
我们只能对这个array进行打印,打印结果为null:
public static void main(String[] agrs)
{
int[] array = null;
System.out.print(array);//null
}
数组拷贝
我们先来自己实现数组的拷贝:
public class textdome{
public static int[] copyArray(int[] array)
{
int[] ret = new int[array.length];
for (int i = 0; i < array.length ; i++) {
ret[i] = array[i];
}
return ret;
}
public static void main(String[] agrs)
{
int[] array = {5,4,3,2,1};
int[] ret = copyArray(array);
System.out.print(Arrays.toString(ret));//[5, 4, 3, 2, 1]
}
}
在java中数组的拷贝有四种方式:
首先用for循环实现的拷贝,
使用Array中的copyOf()方法:
public class textdome{
public static int[] copyArray(int[] array)
{
int[] ret = new int[array.length];
for (int i = 0; i < array.length ; i++) {
ret[i] = array[i];
}
return ret;
}
public static void main(String[] agrs)
{
int[] array = {5,4,3,2,1};
int[] array2 = null;
array2 = Arrays.copyOf(array,array.length);
System.out.println(Arrays.toString(array2));//[5, 4, 3, 2, 1]
}
}
使用 System.arraycopy()来拷贝
我们可以看到这个方法有一个native的修饰词,这个修饰词表示的意思如下:
这个方法是在本地方法栈是实现的,也就是说,而且 使用C/C++的代码实现的,因为C/C++的语言是比较底层的语言,所以在运行的时候速度是非常快的,也就是说买这个System中的arraycopy()方法运行起来是很快的。
使用clone()方法来实现数组的拷贝
public static void main(String[] agrs)
{
int[] array = {5,4,3,2,1};
int[] array2 = array.clone();
System.out.print(Arrays.toString(array2));//[5, 4, 3, 2, 1]
}
对于此处的clone()方法,使用clone之后,相当于是产生了原数组对象的一个副本。这个方法是一个Object的克隆方法。
Object:是所有类的父类,祖先像我们开始写的第一个类 public calss 文件名 这个类的父亲类都是Object。
拷贝分两种情况:
如果通过array2 修改array2 自己的元素,那么不会影响到array1,就叫做深拷贝
比如此时,我们array1和array2 都存储的是某一位置的地址,那么我们通过这个地址来修改对于内存存储的值,这样的话,我们在array1和array2中进行引用修改,都可以修改到内存中地址指向位置的值,那么这种都可以修改到外面的值,这种拷贝就属于浅拷贝。