Java高级-数据结构与算法

1. 排序算法

1.1 冒泡排序

package com.atguigu.java;

public class BubbleSortTest {

    public static void main(String[] args) {
        int[] arr = new int[] { 43, 32, 76, -98, 0, 64, 33, -21, 32, 99 };
        // 冒泡排序
        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 = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }

        }
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
    }
}

 1.2 快速排序

package com.atguigu.java;

import java.text.SimpleDateFormat;
import java.util.Date;

public class QuickSort {

    public static void main(String[] args) {
        // int[] arr = {-9,78,0,23,-567,70, -1,900, 4561};

        // 测试快排的执行速度
        // 创建要给80000个的随机的数组
        int[] arr = new int[8000000];
        for (int i = 0; i < 8000000; i++) {
            arr[i] = (int) (Math.random() * 8000000); // 生成一个[0, 8000000) 数
        }

        System.out.println("排序前");
        Date data1 = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date1Str = simpleDateFormat.format(data1);
        System.out.println("排序前的时间是=" + date1Str);

        quickSort(arr, 0, arr.length - 1);

        Date data2 = new Date();
        String date2Str = simpleDateFormat.format(data2);
        System.out.println("排序前的时间是=" + date2Str);
        // System.out.println("arr=" + Arrays.toString(arr));
    }

    public static void quickSort(int[] arr, int left, int right) {
        int l = left; // 左下标
        int r = right; // 右下标
        // pivot 中轴值
        int pivot = arr[(left + right) / 2];
        int temp = 0; // 临时变量,作为交换时使用
        // while循环的目的是让比pivot 值小放到左边
        // 比pivot 值大放到右边
        while (l < r) {
            // 在pivot的左边一直找,找到大于等于pivot值,才退出
            while (arr[l] < pivot) {
                l += 1;
            }
            // 在pivot的右边一直找,找到小于等于pivot值,才退出
            while (arr[r] > pivot) {
                r -= 1;
            }
            // 如果l >= r说明pivot 的左右两的值,已经按照左边全部是
            // 小于等于pivot值,右边全部是大于等于pivot值
            if (l >= r) {
                break;
            }

            // 交换
            temp = arr[l];
            arr[l] = arr[r];
            arr[r] = temp;

            r -= 1;
            l += 1;

            /*
             * // 如果交换完后,发现这个arr[l] == pivot值 相等 r--, 前移 if (arr[l] == pivot) { r -= 1; } //
             * 如果交换完后,发现这个arr[r] == pivot值 相等 l++, 后移 if (arr[r] == pivot) { l += 1; }
             */
        }

        // 如果 l == r, 必须l++, r--, 否则为出现栈溢出
        if (l == r) {
            l += 1;
            r -= 1;
        }
        // 向左递归
        if (left < r) {
            quickSort(arr, left, r);
        }
        // 向右递归
        if (right > l) {
            quickSort(arr, l, right);
        }

    }

}

 2. 数组模拟环形队

package com.atguigu.queue;

import java.util.Scanner;

public class CircleArrayQueueDemo {

	public static void main(String[] args) {
		
		//测试一把
		System.out.println("测试数组模拟环形队列的案例~~~");
		
		// 创建一个环形队列
		CircleArray queue = new CircleArray(4); //说明设置4, 其队列的有效数据最大是3
		char key = ' '; // 接收用户输入
		Scanner scanner = new Scanner(System.in);//
		boolean loop = true;
		// 输出一个菜单
		while (loop) {
			System.out.println("s(show): 显示队列");
			System.out.println("e(exit): 退出程序");
			System.out.println("a(add): 添加数据到队列");
			System.out.println("g(get): 从队列取出数据");
			System.out.println("h(head): 查看队列头的数据");
			key = scanner.next().charAt(0);// 接收一个字符
			switch (key) {
			case 's':
				queue.showQueue();
				break;
			case 'a':
				System.out.println("输出一个数");
				int value = scanner.nextInt();
				queue.addQueue(value);
				break;
			case 'g': // 取出数据
				try {
					int res = queue.getQueue();
					System.out.printf("取出的数据是%d\n", res);
				} catch (Exception e) {
					// TODO: handle exception
					System.out.println(e.getMessage());
				}
				break;
			case 'h': // 查看队列头的数据
				try {
					int res = queue.headQueue();
					System.out.printf("队列头的数据是%d\n", res);
				} catch (Exception e) {
					// TODO: handle exception
					System.out.println(e.getMessage());
				}
				break;
			case 'e': // 退出
				scanner.close();
				loop = false;
				break;
			default:
				break;
			}
		}
		System.out.println("程序退出~~");
	}

}


class CircleArray {
	private int maxSize; // 表示数组的最大容量
	//front 变量的含义做一个调整: front 就指向队列的第一个元素, 也就是说 arr[front] 就是队列的第一个元素 
	//front 的初始值 = 0
	private int front; 
	//rear 变量的含义做一个调整:rear 指向队列的最后一个元素的后一个位置. 因为希望空出一个空间做为约定.
	//rear 的初始值 = 0
	private int rear; // 队列尾
	private int[] arr; // 该数据用于存放数据, 模拟队列
	
	public CircleArray(int arrMaxSize) {
		maxSize = arrMaxSize;
		arr = new int[maxSize];
	}
	
	// 判断队列是否满
	public boolean isFull() {
		return (rear  + 1) % maxSize == front;
	}
	
	// 判断队列是否为空
	public boolean isEmpty() {
		return rear == front;
	}
	
	// 添加数据到队列
	public void addQueue(int n) {
		// 判断队列是否满
		if (isFull()) {
			System.out.println("队列满,不能加入数据~");
			return;
		}
		//直接将数据加入
		arr[rear] = n;
		//将 rear 后移, 这里必须考虑取模
		rear = (rear + 1) % maxSize;
	}
	
	// 获取队列的数据, 出队列
	public int getQueue() {
		// 判断队列是否空
		if (isEmpty()) {
			// 通过抛出异常
			throw new RuntimeException("队列空,不能取数据");
		}
		// 这里需要分析出 front是指向队列的第一个元素
		// 1. 先把 front 对应的值保留到一个临时变量
		// 2. 将 front 后移, 考虑取模
		// 3. 将临时保存的变量返回
		int value = arr[front];
		front = (front + 1) % maxSize;
		return value;

	}
	
	// 显示队列的所有数据
	public void showQueue() {
		// 遍历
		if (isEmpty()) {
			System.out.println("队列空的,没有数据~~");
			return;
		}
		// 思路:从front开始遍历,遍历多少个元素
		// 动脑筋
		for (int i = front; i < front + size() ; i++) {
			System.out.printf("arr[%d]=%d\n", i % maxSize, arr[i % maxSize]);
		}
	}
	
	// 求出当前队列有效数据的个数
	public int size() {
		// rear = 2
		// front = 1
		// maxSize = 3 
		return (rear + maxSize - front) % maxSize;   
	}
	
	// 显示队列的头数据, 注意不是取出数据
	public int headQueue() {
		// 判断
		if (isEmpty()) {
			throw new RuntimeException("队列空的,没有数据~~");
		}
		return arr[front];
	}
}

3. 单链表应用实例

使用带head头的单向链表实现 –水浒英雄排行榜管理,完成对英雄人物的增删改查操作
第一种方法在添加英雄时,直接添加到链表的尾部
第二种方式在添加英雄时,根据排名将英雄插入到指定位置(如果有这个排名,则添加失败,并给出提示)

 

 

 

代码如下:

package com.atepro.linkedlist;

public 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 + "]";
	}
}
package com.atepro.linkedlist;

import java.util.Stack;

public 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;
	}

	// 第二种方式在添加英雄时,根据排名将英雄插入到指定位置
	// (如果有这个排名,则添加失败,并给出提示)
	public void addByOrder(HeroNode heroNode) {
		// 因为头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置
		// 因为单链表,因为我们找的temp 是位于 添加位置的前一个节点,否则插入不了
		HeroNode temp = head;
		boolean flag = false; // flag标志添加的编号是否存在,默认为false
		while (true) {
			if (temp.next == null) {// 说明temp已经在链表的最后
				break; //
			}
			if (temp.next.no > heroNode.no) { // 位置找到,就在temp的后面插入
				break;
			} else if (temp.next.no == heroNode.no) {// 说明希望添加的heroNode的编号已然存在

				flag = true; // 说明编号存在
				break;
			}
			temp = temp.next; // 后移,遍历当前链表
		}
		// 判断flag 的值
		if (flag) { // 不能添加,说明编号存在
			System.out.printf("准备插入的英雄的编号 %d 已经存在了, 不能加入\n", heroNode.no);
		} else {
			// 插入到链表中, temp的后面
			heroNode.next = temp.next;
			temp.next = heroNode;
		}
	}

	// 修改节点的信息, 根据no编号来修改,即no编号不能改.
	// 说明
	// 1. 根据 newHeroNode 的 no 来修改即可
	public void update(HeroNode newHeroNode) {
		// 判断是否空
		if (head.next == null) {
			System.out.println("链表为空~");
			return;
		}
		// 找到需要修改的节点, 根据no编号
		// 定义一个辅助变量
		HeroNode temp = head.next;
		boolean flag = false; // 表示是否找到该节点
		while (true) {
			if (temp == null) {
				break; // 已经遍历完链表
			}
			if (temp.no == newHeroNode.no) {
				// 找到
				flag = true;
				break;
			}
			temp = temp.next;
		}
		// 根据flag 判断是否找到要修改的节点
		if (flag) {
			temp.name = newHeroNode.name;
			temp.nickname = newHeroNode.nickname;
		} else { // 没有找到
			System.out.printf("没有找到 编号 %d 的节点,不能修改\n", newHeroNode.no);
		}
	}

	// 删除节点
	// 思路
	// 1. head 不能动,因此我们需要一个temp辅助节点找到待删除节点的前一个节点
	// 2. 说明我们在比较时,是temp.next.no 和 需要删除的节点的no比较
	public void del(int no) {
		HeroNode temp = head;
		boolean flag = false; // 标志是否找到待删除节点的
		while (true) {
			if (temp.next == null) { // 已经到链表的最后
				break;
			}
			if (temp.next.no == no) {
				// 找到的待删除节点的前一个节点temp
				flag = true;
				break;
			}
			temp = temp.next; // temp后移,遍历
		}
		// 判断flag
		if (flag) { // 找到
			// 可以删除
			temp.next = temp.next.next;
		} else {
			System.out.printf("要删除的 %d 节点不存在\n", no);
		}
	}

	// 显示链表[遍历]
	public void list() {
		// 判断链表是否为空
		if (head.next == null) {
			System.out.println("链表为空");
			return;
		}
		// 因为头节点,不能动,因此我们需要一个辅助变量来遍历
		HeroNode temp = head.next;
		while (true) {
			// 判断是否到链表最后
			if (temp == null) {
				break;
			}
			// 输出节点的信息
			System.out.println(temp);
			// 将temp后移, 一定小心
			temp = temp.next;
		}
	}
	
	// 方法:获取到单链表的节点的个数(如果是带头结点的链表,需求不统计头节点)
	/**
	 * 
	 * @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;
	}
	
	//查找单链表中的倒数第k个结点 【新浪面试题】
	//思路
	//1. 编写一个方法,接收head节点,同时接收一个index 
	//2. index 表示是倒数第index个节点
	//3. 先把链表从头到尾遍历,得到链表的总的长度 getLength
	//4. 得到size 后,我们从链表的第一个开始遍历 (size-index)个,就可以得到
	//5. 如果找到了,则返回该节点,否则返回nulll
	public static HeroNode findLastIndexNode(HeroNode head, int index) {
		//判断如果链表为空,返回null
		if(head.next == null) {
			return null;//没有找到
		}
		//第一个遍历得到链表的长度(节点个数)
		int size = getLength(head);
		//第二次遍历  size-index 位置,就是我们倒数的第K个节点
		//先做一个index的校验
		if(index <=0 || index > size) {
			return null; 
		}
		//定义给辅助变量, for 循环定位到倒数的index
		HeroNode cur = head.next; //3 // 3 - 1 = 2
		for(int i =0; i< size - index; i++) {
			cur = cur.next;
		}
		return cur;
		
	}
	
	// 方式2:
	// 可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,就实现了逆序打印的效果
	public static void reversePrint(HeroNode head) {
		if (head.next == null) {
			return;// 空链表,不能打印
		}
		// 创建要给一个栈,将各个节点压入栈
		Stack<HeroNode> stack = new Stack<HeroNode>();
		HeroNode cur = head.next;
		// 将链表的所有节点压入栈
		while (cur != null) {
			stack.push(cur);
			cur = cur.next; // cur后移,这样就可以压入下一个节点
		}
		// 将栈中的节点进行打印,pop 出栈
		while (stack.size() > 0) {
			System.out.println(stack.pop()); // stack的特点是先进后出
		}
	}
	
	//将单链表反转
	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;
	}

}
package com.atepro.linkedlist;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestDemo {
	static HeroNode hero1;
	static HeroNode hero2;
	static HeroNode hero3;
	static HeroNode hero4;
	static SingleLinkedList sll;

	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
		hero1 = new HeroNode(1, "宋江", "及时雨");
		hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
		hero3 = new HeroNode(3, "吴用", "智多星");
		hero4 = new HeroNode(4, "林冲", "豹子头");
		//创建要给链表
		sll = new SingleLinkedList();
	}

	@AfterClass
	public static void tearDownAfterClass() throws Exception {
	}

	@Before
	public void setUp() throws Exception {
	}

	@After
	public void tearDown() throws Exception {
	}

	@Test
	public void test_add() {
//		singleLinkedList.add(hero1);
//		singleLinkedList.add(hero4);
//		singleLinkedList.add(hero2);
//		singleLinkedList.add(hero3);
//		System.out.println("原来链表的情况~~");
//		singleLinkedList.list();
	}
	
	@Test
	public void test1_addByOrder() {
		//加入按照编号的顺序
		sll.addByOrder(hero1);
		sll.addByOrder(hero4);
		sll.addByOrder(hero2);
		sll.addByOrder(hero3);
		//显示一把
		sll.list();
	}
	
	@Test
	public void test2_update() {
		HeroNode newHeroNode = new HeroNode(2, "小卢", "玉麒麟~~");
		sll.update(newHeroNode);
		System.out.println("修改后的链表情况~~");
		sll.list();
	}
	
	@Test
	public void test3_del() {
		//删除一个节点
		sll.del(1);
		sll.del(4);
		System.out.println("删除后的链表情况~~");
		sll.list();
	}
	
	@Test
	public void test4_getLength() {
		//测试一下 求单链表中有效节点的个数
		System.out.println("有效的节点个数=" + SingleLinkedList.getLength(sll.getHead()));//2
	}
	
	@Test
	public void test6_reversePrint() {
		System.out.println("测试逆序打印单链表, 没有改变链表的结构~~");
		SingleLinkedList.reversePrint(sll.getHead());		
	}
	
	@Test
	public void test7_reversetList() {
		// 测试一下单链表的反转功能
		System.out.println("原来链表的情况~~");
		sll.list();
		System.out.println("反转单链表~~");
		SingleLinkedList.reversetList(sll.getHead());
		sll.list();
	}

}

4. 约瑟夫问题

public class Josepfu {

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

        // 测试一把小孩出圈是否正确
        circleSingleLinkedList.countBoy(10, 20, 125); // 2->4->1->5->3
        // String str = "7*2*2-5+1-5+3-3";
    }

}

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

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

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

    // 根据用户的输入,计算出小孩出圈的顺序
    /**
     * 
     * @param startNo
     *            表示从第几个小孩开始数数
     * @param countNum
     *            表示数几下
     * @param nums
     *            表示最初有多少小孩在圈中
     */
    public void countBoy(int startNo, int countNum, int nums) {
        // 先对数据进行校验
        if (first == null || startNo < 1 || startNo > nums) {
            System.out.println("参数输入有误, 请重新输入");
            return;
        }
        // 创建要给辅助指针,帮助完成小孩出圈
        Boy 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());

    }
}

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

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

    public int getNo() {
        return no;
    }

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

    public Boy getNext() {
        return next;
    }

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

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值