目录
1.Java多线程:写一下两个线程交替打印 0~100 的奇偶数
1.Java多线程:写一下两个线程交替打印 0~100 的奇偶数
这种实现方式的原理就是线程1打印之后唤醒其他线程,然后让出锁,自己进入休眠状态。因为进入了休眠状态就不会与其他线程抢锁,此时只有线程2在获取锁,所以线程2必然会拿到锁。线程2以同样的逻辑执行,唤醒线程1并让出自己持有的锁,自己进入休眠状态。这样来来回回,持续执行直到任务完成。就达到了两个线程交替获取锁的效果了。
private int count = 0;
private final Object lock = new Object();
public void turning() throws InterruptedException {
Thread even = new Thread(() -> {
while (count <= 100) {
synchronized (lock) {
System.out.println("偶数: " + count++);
lock.notifyAll();
try {
// 如果还没有结束,则让出当前的锁并休眠
if (count <= 100) {
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread odd = new Thread(() -> {
while (count <= 100) {
synchronized (lock) {
System.out.println("奇数: " + count++);
lock.notifyAll();
try {
// 如果还没有结束,则让出当前的锁并休眠
if (count <= 100) {
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
even.start();
// 确保偶数线程线先获取到锁
Thread.sleep(1);
odd.start();
}
2.线程安全的单例模式
双锁判断机制创建单例模式
public class DoubleCheckLockSinglenon {
private static volatile DoubleCheckLockSinglenon doubleCheckLockSingleon = null;
public DoubleCheckLockSinglenon(){}
public static DoubleCheckLockSinglenon getInstance(){
if (null == doubleCheckLockSingleon) {
synchronized(DoubleCheckLockSinglenon.class){
if(null == doubleCheckLockSingleon){
doubleCheckLockSingleon = new DoubleCheckLockSinglenon();
}
}
}
return doubleCheckLockSingleon;
}
public static void main(String[] args) {
System.out.println(DoubleCheckLockSinglenon.getInstance());
}
}
3.用两个栈实现队列
题目描述
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
解题思路
两个栈stack1和stack2 。
开始时,每次添加队尾元素到stack1中。
如果需要弹出队头元素,则将stack1中的元素弹出并push到stack2中,再将stack2的栈顶元素弹出,即为弹出了队头元素。
如果stack2中是非空的,再在队尾中添加元素的时候仍然加到stack1中,从stack2中弹出队头元素。
只有当stack2为空的时候,弹出队头元素才需要将stack1中的元素转移到stack2中。
import java.util.Stack;
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
stack1.push(node);
}
public int pop() {
if(stack2.size() == 0) {
while(!stack1.isEmpty()) {
int temp = stack1.peek();
stack2.push(temp);
stack1.pop();
}
}
int res = stack2.peek();
stack2.pop();
return res;
}
}
4.实现单链表反转操作
单链表是一种常见的数据结构,由一个个节点通过指针方式连接而成,每个节点由两部分组成:一是数据域,用于存储节点数据。二是指针域,用于存储下一个节点的地址。在Java中定义如下:
public class Node {
private Object data;//数据域
private Node next;//指针域
public Node(Object data){
this.data = data;
}
public Node(Object data,Node next){
this.data = data;
this.next = next;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
先说下思路:所谓的单链表反转,就是把每个节点的指针域由原来的指向下一个节点变为指向其前一个节点。但由于单链表没有指向前一个节点的指针域,因此我们需要增加一个指向前一个节点的指针pre,用于存储每一个节点的前一个节点。此外,还需要定义一个保存当前节点的指针cur,以及下一个节点的next。定义好这三个指针后,遍历单链表,将当前节点的指针域指向前一个节点,之后将定义三个指针往后移动,直至遍历到最后一个节点停止。
public static Node reverseListNode(Node head){
//单链表为空或只有一个节点,直接返回原单链表
if (head == null || head.getNext() == null){
return head;
}
//前一个节点指针
Node preNode = null;
//当前节点指针
Node curNode = head;
//下一个节点指针
Node nextNode = null;
while (curNode != null){
nextNode = curNode.getNext();//nextNode 指向下一个节点
curNode.setNext(preNode);//将当前节点next域指向前一个节点
preNode = curNode;//preNode 指针向后移动
curNode = nextNode;//curNode指针向后移动
}
return preNode;
}
5.Java实现二分查找
二分查找又称折半查找,查找效率不错
适用场景:顺序存储结构且按有序排列,这也是它的缺点。
/*
*递归实现二分算法
*/
public static int binSearch_2(int key,int[] array,int low,int high){
//防越界
if (key < array[low] || key > array[high] || low > high) {
return -1;
}
int middle = (low+high)/2;
if(array[middle]>key){
//大于关键字
return binSearch_2(key,array,low,middle-1);
}else if(array[middle]<key){
//小于关键字
return binSearch_2(key,array,middle+1,high);
}else{
return array[middle];
}
}
6.冒泡排序
N个数字要排序完成,总共进行N-1趟排序,每i趟的排序次数为(N-i)次,所以可以用双重循环语句,外层控制循环多少趟,内层控制每一趟的循环次数,即
/*
* 冒泡排序
*/
public class BubbleSort {
public static void main(String[] args) {
int[] arr={6,3,8,2,9,1};
System.out.println("排序前数组为:");
for(int num:arr){
System.out.print(num+" ");
}
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;
}
}
}
System.out.println();
System.out.println("排序后的数组为:");
for(int num:arr){
System.out.print(num+" ");
}
}
}
用时间复杂度来说:
1.如果我们的数据正序,只需要走一趟即可完成排序。所需的比较次数C和记录移动次数M均达到最小值,即:Cmin=n-1;Mmin=0;所以,冒泡排序最好的时间复杂度为O(n)。
2.如果很不幸我们的数据是反序的,则需要进行n-1趟排序。每趟排序要进行n-i次比较(1≤i≤n-1),且每次比较都必须移动记录三次来达到交换记录位置。在这种情况下,比较和移动次数均达到最大值:
冒泡排序的最坏时间复杂度为:O(n2) 。
综上所述:冒泡排序总的平均时间复杂度为:O(n2) 。
7.快速排序
快速排序的基本思想:
1). 先从数列中取出一个数作为基准数。
2). 分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
3). 再对左右区间重复第二步,直到各区间只有一个数。
public class Main {
//快排实现方法
public static void quickRow(int[] array,int low,int high) {
int i,j,pivot;
//结束条件
if(low >= high) {
return;
}
i = low;
j = high;
//选择的节点,这里选择的数组的第一数作为节点
pivot = array[low];
while(i<j) {
//从右往左找比节点小的数,循环结束要么找到了,要么i=j
while(array[j] >= pivot && i<j) {
j--;
}
//从左往右找比节点大的数,循环结束要么找到了,要么i=j
while(array[i] <= pivot && i<j) {
i++;
}
//如果i!=j说明都找到了,就交换这两个数
if(i<j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
//i==j一轮循环结束,交换节点的数和相遇点的数
array[low] = array[i];
array[i] = pivot;
//数组“分两半”,再重复上面的操作
quickRow(array,low,i-1);
quickRow(array,i+1,high);
}
public static void main(String[] args) {
int[] array = {6,17,38,59,2,10};
int low = 0,high = array.length-1;
quickRow(array,low,high);
for (int i : array){
System.out.println(i);
}
}
}
8.Java单链表实现快速排序
单链表的实现为:
(1)使第一个节点为中心点
(2)创建2个指针(p,q),p指向头结点,q指向p的下一个节点
(3)q开始遍历,如果发现q的值比中心点的值小,则此时p=p->next,并且执行当前p的值和q的值交换,q遍历到链表尾即可
(4)把头结点的值和p的值执行交换。此时p节点为中心点,并且完成1轮快排
(5)使用递归的方法即可完成排序
public static void main(String[] args) {
ListNode head = new ListNode(2);
ListNode l1 = new ListNode(2);
ListNode l2 = new ListNode(5);
ListNode l3 = new ListNode(3);
ListNode l4 = new ListNode(8);
ListNode l5 = new ListNode(4);
ListNode l6 = new ListNode(2);
ListNode l7 = new ListNode(1);
/*ListNode p = l1;
System.out.println(p.equals(head));
System.out.println(p == head);*/
head.next = l1;
l1.next = l2;
l2.next = l3;
l3.next = l4;
l4.next = l5;
l5.next = l6;
l6.next = l7;
l7.next = null;
ListNode p = head;
while (p.next != null) {
System.out.print(p.val);
p = p.next;
}
System.out.print(p.val);
System.out.println();
ListNode begin = head, end = p;
new SingleLinkedListSorting().quickSort(begin, end);
p = head;
while (p != null) {
System.out.print(p.val);
p = p.next;
}
}
public void quickSort(ListNode begin, ListNode end) {
//判断为空,判断是不是只有一个节点
if (begin == null || end == null || begin == end)
return;
//从第一个节点和第一个节点的后面一个几点
ListNode first = begin;
ListNode second = begin.next;
int nMidValue = begin.val;
//结束条件,second到最后了
while (second != end.next && second != null) {
if (second.val < nMidValue) {
first = first.next;
//判断一下,避免后面的数比第一个数小,不用换的局面
if (first != second) {
int temp = first.val;
first.val = second.val;
second.val = temp;
}
}
second = second.next;
}
//判断,有些情况是不用换的,提升性能
if (begin != first) {
int temp = begin.val;
begin.val = first.val;
first.val = temp;
}
//前部分递归
quickSort(begin, first);
//后部分递归
quickSort(first.next, end);
}
9.二叉树的前序遍历
前序遍历(DLR,lchild,data,rchild),是二叉树遍历的一种,也叫做先根遍历、先序遍历、前序周游,可记做根左右。前序遍历首先访问根结点然后遍历左子树,最后遍历右子树。
package test;
//前序遍历的递归实现与非递归实现
import java.util.Stack;
public class Test
{
public static void main(String[] args)
{
TreeNode[] node = new TreeNode[10];//以数组形式生成一棵完全二叉树
for(int i = 0; i < 10; i++)
{
node[i] = new TreeNode(i);
}
for(int i = 0; i < 10; i++)
{
if(i*2+1 < 10)
node[i].left = node[i*2+1];
if(i*2+2 < 10)
node[i].right = node[i*2+2];
}
preOrderRe(node[0]);
}
public static void preOrderRe(TreeNode biTree)
{//递归实现
System.out.println(biTree.value);
TreeNode leftTree = biTree.left;
if(leftTree != null)
{
preOrderRe(leftTree);
}
TreeNode rightTree = biTree.right;
if(rightTree != null)
{
preOrderRe(rightTree);
}
}
public static void preOrder(TreeNode biTree)
{//非递归实现
Stack<TreeNode> stack = new Stack<TreeNode>();
while(biTree != null || !stack.isEmpty())
{
while(biTree != null)
{
System.out.println(biTree.value);
stack.push(biTree);
biTree = biTree.left;
}
if(!stack.isEmpty())
{
biTree = stack.pop();
biTree = biTree.right;
}
}
}
}
class TreeNode//节点结构
{
int value;
TreeNode left;
TreeNode right;
TreeNode(int value)
{
this.value = value;
}
}
10.二叉树的中序遍历
中序遍历(LDR)是 二叉树遍历的一种,也叫做 中根遍历、中序周游。在二叉树中,先左后根再右。巧记:左根右。
import java.util.Stack;
public class Test
{
public static void main(String[] args)
{
TreeNode[] node = new TreeNode[10];//以数组形式生成一棵完全二叉树
for(int i = 0; i < 10; i++)
{
node[i] = new TreeNode(i);
}
for(int i = 0; i < 10; i++)
{
if(i*2+1 < 10)
node[i].left = node[i*2+1];
if(i*2+2 < 10)
node[i].right = node[i*2+2];
}
midOrderRe(node[0]);
System.out.println();
midOrder(node[0]);
}
public static void midOrderRe(TreeNode biTree)
{//中序遍历递归实现
if(biTree == null)
return;
else
{
midOrderRe(biTree.left);
System.out.println(biTree.value);
midOrderRe(biTree.right);
}
}
public static void midOrder(TreeNode biTree)
{//中序遍历费递归实现
Stack<TreeNode> stack = new Stack<TreeNode>();
while(biTree != null || !stack.isEmpty())
{
while(biTree != null)
{
stack.push(biTree);
biTree = biTree.left;
}
if(!stack.isEmpty())
{
biTree = stack.pop();
System.out.println(biTree.value);
biTree = biTree.right;
}
}
}
}
class TreeNode//节点结构
{
int value;
TreeNode left;
TreeNode right;
TreeNode(int value)
{
this.value = value;
}
}
11.二叉树的后序遍历
后序遍历(LRD)是 二叉树遍历的一种,也叫做 后根遍历、后序周游,可记做左右根。后序遍历有 递归算法和非递归算法两种。在二叉树中,先左后右再根。巧记:左右根。
import java.util.Stack;
public class Test
{
public static void main(String[] args)
{
TreeNode[] node = new TreeNode[10];//以数组形式生成一棵完全二叉树
for(int i = 0; i < 10; i++)
{
node[i] = new TreeNode(i);
}
for(int i = 0; i < 10; i++)
{
if(i*2+1 < 10)
node[i].left = node[i*2+1];
if(i*2+2 < 10)
node[i].right = node[i*2+2];
}
postOrderRe(node[0]);
System.out.println("***");
postOrder(node[0]);
}
public static void postOrderRe(TreeNode biTree)
{//后序遍历递归实现
if(biTree == null)
return;
else
{
postOrderRe(biTree.left);
postOrderRe(biTree.right);
System.out.println(biTree.value);
}
}
public static void postOrder(TreeNode biTree)
{//后序遍历非递归实现
LinkedList<TreeNode> stack = new LinkedList<TreeNode>();
// 指向上一个被访问的节点
TreeNode pre = null;
while(biTree!=null || !stack.isEmpty()){
// 遍历到左下
while(biTree!=null){
stack.push(biTree);
biTree = biTree.left;
}
biTree = stack.pop();
if(biTree.right==null || biTree.right == pre){
System.out.println(biTree.value);
pre = biTree;
// 保证了不会再次遍历已经遍历过的左子树
biTree= null;
}else{
stack.push(biTree);
biTree= biTree.right;
}
}
}
}
class TreeNode//节点结构
{
int value;
TreeNode left;
TreeNode right;
TreeNode(int value)
{
this.value = value;
}
}
12.java实现逆波兰表达式
逆波兰表达式把运算量写在前面,把算符写在后面。
private static List<String> sign=new ArrayList<>();
static {
sign.add("+");
sign.add("-");
sign.add("*");
sign.add("/");
}
public static void main(String[] args) {
String[] arrs={"2", "1", "+", "3", "*"};
System.out.println(evalRPN(arrs)); //9
String[] arrs1={"4", "13", "5", "/", "+"};
System.out.println(evalRPN(arrs1)); //6
}
public static int evalRPN(String[] tokens) {
Stack<Integer> mathStack=new Stack<>();
for (int i = 0; i <tokens.length ; i++) {
String s=tokens[i];
if(sign.contains(s)){
Integer sum=0;
Integer num2=mathStack.pop();
Integer num1=mathStack.pop();
if("+".equals(s)){
sum=num1+num2;
}else if("-".equals(s)){
sum=num1-num2;
}else if("*".equals(s)){
sum=num1*num2;
}else if("/".equals(s)){
sum=num1/num2;
}
// System.out.println(num1+s+num2+"="+sum);
mathStack.push(sum);
}else {
mathStack.push(Integer.parseInt(s));
}
}
return mathStack.pop();
}
13.斐波那契数列及青蛙跳台阶问题
递归方式:
public int Fibonacci1(int n) {
if (n <= 0) return 0;
if (n == 1) return 1;
return Fibonacci1(n-1) + Fibonacci1(n - 2);
}
非递归:
public int Fibonacci2(int n) {
if (n <= 0) return 0;
if (n == 1) return 1;
int i=0;int j=1;
int res=0;
for(int x=2;x<=n;x++){
res = i+j;
i=j;
j=res;
}
return res;
}