JAVA数据结构与算法(一)

数据结构

数据结构包括:线性结构非线性结前端

线性结构java

1)线性结构做为最经常使用的数据结构,其特色是数据元素之间存在一对一的线性关系(一维数组)node

2) 线性结 构有 两种不一样的存储结构,即顺序存储结构(数组)和链式存储结构(链表)。顺序存储的线性表称为顺序表,顺序表中的存储元素是连续
3) 式存储的线性表称为链表,链表中的存储元素不必定是连续的,元素节点中存放数据元素以及相邻元素的地址信
4) 线性结 见的有: 队列、链表和栈。
 

稀疏数组sparsearray

当一个数组中大部分元素为0,或者为同一个值的数组时,可使用稀疏数组来保存该数组。面试

疏数组的处理方法是:算法

1) 录数组一共有几行几列,有多少个不一样的
2) 具备不一样值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规
 

应用实例:五子棋的棋盘保存

 

public class SparseArray {

	public static void main(String[] args) {
		// 建立一个原始的二维数组 11 * 11
		// 0: 表示没有棋子, 1 表示 黑子 2 表蓝子
		int chessArr1[][] = new int[11][11];
		chessArr1[1][2] = 1;
		chessArr1[2][3] = 2;
		chessArr1[4][5] = 2;
		// 输出原始的二维数组
		System.out.println("原始的二维数组~~");
		for (int[] row : chessArr1) {
			for (int data : row) {
				System.out.printf("%d\t", data);
			}
			System.out.println();
		}
//=====================================================================

		// 将二维数组 转 稀疏数组的思
		// 1. 先遍历二维数组 获得非0数据的个数
		int sum = 0;
		for (int i = 0; i < 11; i++) {
			for (int j = 0; j < 11; j++) {
				if (chessArr1[i][j] != 0) {
					sum++;
				}
			}
		}
		// 2. 建立对应的稀疏数组
		int sparseArr[][] = new int[sum + 1][3];
		// 给稀疏数组赋值
		sparseArr[0][0] = 11;
		sparseArr[0][1] = 11;
		sparseArr[0][2] = sum;	
		// 遍历二维数组,将非0的值存放到 sparseArr中
		int count = 0; //count 用于记录是第几个非0数据
		for (int i = 0; i < 11; i++) {
			for (int j = 0; j < 11; j++) {
				if (chessArr1[i][j] != 0) {
					count++;
					sparseArr[count][0] = i;
					sparseArr[count][1] = j;
					sparseArr[count][2] = chessArr1[i][j];
				}
			}
		}		
		// 输出稀疏数组的形式
		System.out.println();
		System.out.println("获得稀疏数组为~~~~");
		for (int i = 0; i < sparseArr.length; i++) {
			System.out.printf("%d\t%d\t%d\t\n", sparseArr[i][0], sparseArr[i][1], sparseArr[i][2]);
		}
		System.out.println();

	}

}

//======================================================================
		//将稀疏数组 --》 恢复成 原始的二维数组
		/*
		 *  1. 先读取稀疏数组的第一行,根据第一行的数据,建立原始的二维数组
			2. 在读取稀疏数组后几行的数据,并赋给 原始的二维数组 便可.
		 */
		
		//1. 先读取稀疏数组的第一行,根据第一行的数据,建立原始的二维数组
		
		int chessArr2[][] = new int[sparseArr[0][0]][sparseArr[0][1]];
		
		//2. 在读取稀疏数组后几行的数据(从第二行开始),并赋给 原始的二维数组 便可
		
		for(int i = 1; i < sparseArr.length; i++) {
			chessArr2[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
		}
		
		// 输出恢复后的二维数组
		System.out.println();
		System.out.println("恢复后的二维数组");
		
		for (int[] row : chessArr2) {
			for (int data : row) {
				System.out.printf("%d\t", data);
			}
			System.out.println();
		}

队列

列是一个有序列表,能够用 数组 或是 链表 来实现
先入先出 的原则。即 :先 入队 列的数据,要先取出。后存入的要后取

用数组模拟队列

// 使用数组模拟队列-编写一个ArrayQueue类
class ArrayQueue {
	private int maxSize; // 表示数组的最大容量
	private int front; // 队列头
	private int rear; // 队列尾
	private int[] arr; // 该数据用于存放数据, 模拟队列

// 建立队列的构造器
	public ArrayQueue(int arrMaxSize) {
		maxSize = arrMaxSize;
		arr = new int[maxSize];
		front = -1; // 指向队列头部,分析出front是指向队列头的前一个位置.
		rear = -1; // 指向队列尾,指向队列尾的数据(即就是队列最后一个数据)
	}
	// 判断队列是否满
	public boolean isFull() {
		return rear == maxSize - 1;
	}

	// 判断队列是否为空
	public boolean isEmpty() {
		return rear == front;
	}


    // 入列
	public void addQueue(int n) {
		// 判断队列是否满
		if (isFull()) {
			System.out.println("队列满,不能加入数据~");
			return;
		}
		rear++; // 让rear 后移
		arr[rear] = n;
	}

   //出列
	public int getQueue() {
		// 判断队列是否空
		if (isEmpty()) {
			// 经过抛出异常
			throw new RuntimeException("队列空,不能取数据");
		}
		front++; // front后移
		return arr[front];

	}

	// 显示队列的全部数据
	public void showQueue() {
		// 遍历
		if (isEmpty()) {
			System.out.println("队列空的,没有数据~~");
			return;
		}
		for (int i = 0; i < arr.length; i++) {
			System.out.printf("arr[%d]=%d\n", i, arr[i]);
		}
	}

public static void main(String[] args) {
		//建立一个队列
		ArrayQueue queue = new ArrayQueue(3);
       addQueue(3);
       addQueue(5);
       addQueue(7);
      showQueue();


}

数组模拟环形队列

对前面的数组模拟队列的优化,充分利用数组,所以将数组看作是一个环形的。小程序

思路以下:数组

1.front的含义作一个调整:front就指向队列的第一个元素,也就是说arr【front】就是队列的第一个元素。数据结构

2.rear变量的含义作一个调整:rear指向队列的最后一个元素的后一个位置。由于但愿空出一个空间作约定。ide

3.当队列满时,(队尾下标+1)%数组长度=队头下标测试

4.队列为空的条件,rear==front

5.当咱们这样分析,队列有效的数据的个数(rear+maxSize-front)%maxSize

链表

表是有序的列表,可是它在内存中是存储以下

:

1) 链表是以节点的方式来存储 , 是链式存储
2) 个节点包含 data 域, next 域:指向下一个节点 .
3) 图:发现链表的 各个节点不必定是连续存储 .
4) 表分带头节点的链表和没有头节点的链表,根据实际的需求来肯定

单向链表的代码实现

使用带head头的单向链表实现 水浒英雄排行榜管

1)在添加英雄时,直接添加到链表的尾部

 
//定义HeroNode , 每一个HeroNode 对象就是一个节点
class HeroNode {
	public int no;
	public String name;
	public String nickname;
	public HeroNode next; //指向下一个节点
	//构造器
	public HeroNode(int no, String name, String nickname) {
		this.no = no;
		this.name = name;
		this.nickname = nickname;
	}
	//为了显示方法,咱们从新toString
	@Override
	public String toString() {
		return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
	}
	
}
//定义SingleLinkedList 管理咱们的英雄
class SingleLinkedList {
	//先初始化一个头节点, 头节点不要动, 不存放具体的数据
	private HeroNode head = new HeroNode(0, "", "");
	
	
	//返回头节点
	public HeroNode getHead() {
		return head;
	}

	//添加节点到单向链表
	//思路,当不考虑编号顺序时
	//1. 找到当前链表的最后节点
	//2. 将最后这个节点的next 指向 新的节点
	public void add(HeroNode heroNode) {
		
		//由于head节点不能动,所以咱们须要一个辅助遍历 temp
		HeroNode temp = head;
		//遍历链表,找到最后
		while(true) {
			//找到链表的最后
			if(temp.next == null) {//
				break;
			}
			//若是没有找到最后, 将将temp后移
			temp = temp.next;
		}
		//当退出while循环时,temp就指向了链表的最后
		//将最后这个节点的next 指向 新的节点
		temp.next = heroNode;
	}

单链表面试题

1)求单链表中有效节点的个数

/**
* @param head 链表的头结点
* @return 返回的就是有效节点的个数
*/

public static int getLength(HeroNode head){
   if(head.next == null) { //空链表
			return 0;
		}
		int length = 0;
		//定义一个辅助的变量, 这里咱们没有统计头节点
		HeroNode cur = head.next;
		while(cur != null) {
			length++;
			cur = cur.next; //遍历
		}
		return length;
	}
}

2)将单链表反转

public static void reversetList(HeroNode head) {
//若是当前链表为空,或者只有一个节点,无需反转,直接返回
		if(head.next == null || head.next.next == null) {
			return ;
		}
//定义一个辅助的指针(变量),帮助咱们遍历原来的链表
		HeroNode cur = head.next;
        HeroNode next = null;// 指向当前节点[cur]的下一个节点
        HeroNode reverseHead = new HeroNode(0, "", "");

//遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead 的最前端
        while(cur != null) { 
			next = cur.next;//先暂时保存当前节点的下一个节点,由于后面须要使用
			cur.next = reverseHead.next;//将cur的下一个节点指向新的链表的最前端
			reverseHead.next = cur; //将cur 链接到新的链表上
			cur = next;//让cur后移
		}
//将head.next 指向 reverseHead.next , 实现单链表的反转
		head.next = reverseHead.next;
	}

单项环形链表应用场景

Josephu(约瑟夫、约瑟夫环问题

Josephu  问题为:设编号为12… nn我的围坐一圈,约定编号为k1<=k<=n)的人从1开始报数,数到m 的那我的出列,它的下一位又从1开始报数,数到m的那我的又出列,依次类推,直到全部人出列为止,由此产生一个出队编号的序列

:用一个不带头结点的循环链表来处理Josephu 问题:先构成一个有n个结点的单循环链表,而后由k结点起从1开始计数,计到m时,对应结点从链表中删除,而后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束。

构建一个单向的环形链表思路

1. 先建立第一个节点, first 指向该节点,并造成环形

2. 后面当咱们每建立一个新的节点,就把该节点,加入到已有的环形链表中便可.

 

遍历环形链表

1. 先让一个辅助指针(变量) curBoy,指向first节点

2. 而后经过一个while循环遍历 该环形链表便可 curBoy.next  == first 结束

// 建立一个Node类,表示一个节点
class Node{
	private int no;// 编号
	private Node next; // 指向下一个节点,默认null

	public Node(int no) {
		this.no = no;
	}

	public int getNo() {
		return no;
	}

	public void setNo(int no) {
		this.no = no;
	}

	public Node getNext() {
		return next;
	}

	public void setNext(Node next) {
		this.next = next;
	}

}
// 建立一个环形的单向链表
class CircleSingleLinkedList {
	// 建立一个first节点,当前没有编号
	private Node first = null;

	// 添加小孩节点,构建成一个环形的链表
	public void addNode(int nums) {
		// nums 作一个数据校验
		if (nums < 1) {
			System.out.println("nums的值不正确");
			return;
		}
		Node curNode = null; // 辅助指针,帮助构建环形链表
		// 使用for来建立咱们的环形链表
		for (int i = 1; i <= nums; i++) {
			// 根据编号,建立小孩节点
			Node node = new Node(i);
			// 若是是第一个小孩
			if (i == 1) {
				first = node;
				first.setNext(first); // 构成环
				curNode = first; // 让curNode指向第一个节点
			} else {
				curNode.setNext(node);//
				boy.setNext(first);//
				curNode = node;
			}
		}
	}

	// 遍历当前的环形链表
	public void showNode() {
		// 判断链表是否为空
		if (first == null) {
			System.out.println("没有任何节点~~");
			return;
		}
		// 由于first不能动,所以咱们仍然使用一个辅助指针完成遍历
		Node curNode = first;
		while (true) {
			System.out.printf("节点的编号 %d \n", curNode.getNo());
			if (curNode.getNext() == first) {// 说明已经遍历完毕
				break;
			}
			curNode = curNode.getNext(); // curNode后移
		}
	}

	// 根据用户的输入,计算出小孩出圈的顺序
	/**
	 * 
	 * @param startNo
	 *            表示从第几个小孩开始数数
	 * @param countNum
	 *            表示数几下
	 * @param nums
	 *            表示最初有多少小孩在圈中
	 */
	public void countNode(int startNo, int countNum, int nums) {
		// 先对数据进行校验
		if (first == null || startNo < 1 || startNo > nums) {
			System.out.println("参数输入有误, 请从新输入");
			return;
		}
		// 建立要给辅助指针,帮助完成小孩出圈
		Node helper = first;
		// 需求建立一个辅助指针(变量) helper , 事先应该指向环形链表的最后这个节点
		while (true) {
			if (helper.getNext() == first) { // 说明helper指向最后小孩节点
				break;
			}
			helper = helper.getNext();
		}
		//节点报数前,先让 first 和  helper 移动 k - 1次
		for(int j = 0; j < startNo - 1; j++) {
			first = first.getNext();
			helper = helper.getNext();
		}
		//当节点报数时,让first 和 helper 指针同时 的移动  m  - 1 次, 而后出圈
		//这里是一个循环操做,知道圈中只有一个节点
		while(true) {
			if(helper == first) { //说明圈中只有一个节点
				break;
			}
			//让 first 和 helper 指针同时 的移动 countNum - 1
			for(int j = 0; j < countNum - 1; j++) {
				first = first.getNext();
				helper = helper.getNext();
			}
			//这时first指向的节点,就是要出圈的节点
			System.out.printf("节点%d出圈\n", first.getNo());
			//这时将first指向的节点出圈
			first = first.getNext();
			helper.setNext(first); //
			
		}
		System.out.printf("最后留在圈中的编号%d \n", first.getNo());
		
	}
}

 测试:

public static void main(String[] args) {
        // 测试一把看看构建环形链表,和遍历是否ok
        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        circleSingleLinkedList.addNode(125);// 加入5个小孩节点
        circleSingleLinkedList.showNode();

        //测试一把小孩出圈是否正确
        circleSingleLinkedList.countNode(10, 5, 25); 
        
    }

 

 

经典算法面试题

字符串匹配问题::

1) 有一个字符串 str1= "" bbc abcdab abcdabcdabcde "" ,和一个子串 str2="abcdabc "
2) 如今要判断 str1 是否含有 str2 , 若是存在,就返回第一次出现的位置 , 若是没有,则返回 - 1
3) 求用最快的速度来完成匹配
4) 的思路是什么?
答:KMP算法

 

塔游戏, 请完成汉诺塔游戏的代码: 要求:

1) A塔的全部圆盘移动到C塔。而且规定,小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆

答:分治算法.

 

 

皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即:意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法【92】

答:回溯算法

 

踏棋盘算法介绍和游戏演示

1) 踏棋盘算 被称为骑 士周游问
2) 马随机放在国际象棋的 8×8 棋盘 Board[0 7][0 7] 的某个方格中,马按走棋规 ( 马走日字 ) 行移动。要求每一个方格只进入一次,走遍棋盘上所有 64 个方
答:图的深度优化遍历算法(DFS) + 贪心算法优化
相关文章
相关标签/搜索