手撕栈(使用数组)
public class 栈 {
//用数组实现栈
private int[] storage;
//栈的容量
private int capacity;
//栈中元素数量
private int count;
//扩容因子
private final int GROW_FACTOR = 2;
public 栈(){
this.capacity = 8;
storage = new int[capacity];
this.count = 0;
}
public 栈(int capacity){
if(capacity < 1){
throw new IllegalArgumentException("Capacity too small.");
}
this.capacity = capacity;
storage = new int[capacity];
this.count = 0;
}
//确保容量大小
private void ensureCapacity() {
int newCapacity = capacity * GROW_FACTOR;
storage = Arrays.copyOf(storage, newCapacity);
capacity = newCapacity;
}
private void push(int value){
if(count == capacity){
ensureCapacity();
}
storage[++count] = value;
}
private int pop(){
if(count == 0){
throw new IllegalArgumentException("Stack is empty.");
}
count--;
return storage[count];
}
private int peek(){
if(count == 0){
throw new IllegalArgumentException("Stack is empty.");
}
return storage[count];
}
//判断栈是否为空
private boolean isEmpty() {
return count == 0;
}
//返回栈中元素的个数
private int size() {
return count;
}
}
手撕队列(使用链表)
public class 队列 {
private Node headNode;
private Node lastNode;
private int count;
public 队列(){
this.count = 0;
this.headNode = new Node(-1);
}
public void push(int val){
if(count == 0){
headNode.next = new Node(val);
lastNode = headNode.next;
}else {
lastNode.next = new Node(val);
lastNode = lastNode.next;
}
count++;
}
public int pop(){
if(count < 1){
throw new IllegalArgumentException("Entity is empty..");
}
count--;
int r = headNode.next.val;
headNode.next = headNode.next.next;
return r;
}
public int peek(){
if(count < 1){
throw new IllegalArgumentException("Entity is empty..");
}
return headNode.next.val;
}
}
class Node{
public int val;
public Node next;
public Node(int val){
this.val = val;
}
}
手撕前缀树
class Trie {
private TrieNode t;
public Trie() {
t = new TrieNode();
}
public void insert(String word) {
TrieNode t1 = t;
for(int i = 0;i < word.length();i++){
char c = word.charAt(i);
TrieNode subNode = t1.getSubNode(c);
if (subNode == null) {
subNode = new TrieNode(); // 初始化子节点
t1.addSubNode(c, subNode); // 添加子节点
}
t1 = subNode;
if (i == word.length() - 1) {
t1.setKeywordEnd(true);
}
}
}
public boolean search(String word) {
TrieNode tempNode = t;
// 指针 2:指向文本中某个敏感词的第一位
int begin = 0;
// 指针 3;指向文本中某个敏感词的最后一位
int end = 0;
while (end < word.length()) {
char c = word.charAt(end);
tempNode = tempNode.getSubNode(c);
if (tempNode == null) {
return false;
}
//前缀树最后一位但是此时单词还没到最后一位
else if (tempNode.isKeywordEnd() ) {
if(end != word.length() - 1){
if(tempNode.getSubNode(word.charAt(end + 1)) == null){
return false;
}else{
end++;
}
}else{
return true;
}
}
//是敏感词且不是最后一位
else {
// 检查下一个字符
end ++;
}
}
return false;
}
public boolean startsWith(String prefix) {
TrieNode tempNode = t;
// 指针 2:指向文本中某个敏感词的第一位
int begin = 0;
// 指针 3;指向文本中某个敏感词的最后一位
int end = 0;
while (end < prefix.length()) {
char c = prefix.charAt(end);
tempNode = tempNode.getSubNode(c);
if (tempNode == null) {
return false;
}
end++;
}
return true;
}
}
class TrieNode {
// 关键词结束标识(叶子节点)
private boolean isKeywordEnd = false;
// 子节点(key:子节点字符, value:子节点类型)
private Map<Character, TrieNode> subNodes = new ConcurrentHashMap<>();
//树是否为空
public boolean isEmpty(){
return subNodes.size() == 0;
}
//是否为关键词结束标识
public boolean isKeywordEnd() {
return isKeywordEnd;
}
//设置关键词结束标识
public void setKeywordEnd(boolean keywordEnd) {
isKeywordEnd = keywordEnd;
}
// 添加子节点
public void addSubNode(Character c, TrieNode node) {
subNodes.put(c, node);
}
// 获取子节点
public TrieNode getSubNode(Character c) {
return subNodes.get(c);
}
}
归并排序
使用分治法,将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为 2 - 路归并。
算法步骤
归并排序算法是一个递归过程,边界条件为当输入序列仅有一个元素时,直接返回,具体过程如下:
- 如果输入内只有一个元素,则直接返回,否则将长度为
n
的输入序列分成两个长度为n/2
的子序列; - 分别对这两个子序列进行归并排序**(递归算法)**,使子序列变为有序状态;
- 设定两个指针,分别指向两个已经排序子序列的起始位置;
- 比较两个指针所指向的元素,选择相对小的元素放入到合并空间(用于存放排序结果),并移动指针到下一位置;
- 重复步骤 3 ~4 直到某一指针达到序列尾;
- 将另一序列剩下的所有元素直接复制到合并序列尾。
图解算法
代码实现
public static void mergeSort(int[]arr) {
sort(arr,0, arr.length - 1);
}
public static void sort(int[] arr, int l, int r) {
if(l == r){
return;
}
int mid = l + ((r - l) >> 1);
sort(arr,l,mid);
sort(arr,mid + 1,r);
merge(arr,l,mid,r);
}
public static void merge(int[] arr, int l, int mid,int r) {
int[] temp = new int[r - l + 1];
//temp数组的下标
int i = 0;
//左边那个数组的下标
int index_1 = l;
//右边那个数组的下标
int index_2 = mid + 1;
while (index_1 <= mid && index_2 <= r){
if(arr[index_1] > arr[index_2]){
temp[i] = arr[index_2];
index_2++;
}else {
temp[i] = arr[index_1];
index_1++;
}
i++;
}
if(index_1 <= mid){
while (index_1 < mid + 1){
temp[i] = arr[index_1];
index_1++;
i++;
}
}else {
while (index_2 <= r){
temp[i] = arr[index_2];
index_2++;
i++;
}
}
//使用arraycopy进行数组注入
System.arraycopy(arr,l,temp,0,r - l + 1);
}
算法分析
- 稳定性:稳定
- 时间复杂度:最佳:O(nlogn), 最差:O(nlogn), 平均:O(nlogn)
- 空间复杂度:O(n)
快速排序
可移至使用 Java 实现快速排序(详解) - 个人文章 - SegmentFault 思否进行分析
也是使用了分治思想,不同的是快速排序在划分子问题的时候经过多一步处理,将划分的两组数据划分为一大一小,这样在最后合并的时候就不必像归并排序那样再进行比较。但也正因为如此,划分的不定性使得快速排序的时间复杂度并不稳定。
快速排序的基本思想:通过一趟排序将待排序列分隔成独立的两部分,其中一部分记录的元素均比另一部分的元素小,则可分别对这两部分子序列继续进行排序,以达到整个序列有序。
算法步骤
- 从序列中随机挑出一个元素,做为 “基准”(
pivot
); - 重新排列序列,将所有比基准值小的元素摆放在基准前面,所有比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个操作结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地把小于基准值元素的子序列和大于基准值元素的子序列进行快速排序
图解算法
代码实现
public static void quickSort(int[] data) {
Sort(data,0,data.length - 1);
}
public static void Sort(int[] data, int low, int high) {
//一共需要3个指针,
// t为temp指针
// j为比较指针,每次比较后需要修改
// i为二次指针,比较值第一次大于基准时进行赋值,之后只有j指针指向的比较值大于当前基准才需要调换位置并++
int i, j, temp, t;
if (low > high) {
return;
}
i = low;
j = high;
//这里以low位置作为基准
temp = data[low];
while (i < j){
while (temp <= data[j] && i < j){
j--;
}
while (temp >= data[i] && i < j){
i++;
}
if(i < j){
t = data[j];
data[j] = data[i];
data[i] = t;
}
}
data[low] = data[i];
data[i] = temp;
Sort(data,low,j - 1);
Sort(data,j + 1,high);
}
算法分析
- 稳定性:不稳定
- 时间复杂度:最佳:O(nlogn), 最差:O(nlogn),平均:O(nlogn)
- 空间复杂度:O(nlogn)
堆排序
需要手撕堆的知识,使用堆取出元素即可排序
算法步骤
- 将初始待排序列
(R1, R2, ……, Rn)
构建成大顶堆,此堆为初始的无序区; - 将堆顶元素
R[1]
与最后一个元素R[n]
交换,此时得到新的无序区(R1, R2, ……, Rn-1)
和新的有序区 (Rn), 且满足R[1, 2, ……, n-1]<=R[n]
; - 由于交换后新的堆顶
R[1]
可能违反堆的性质,因此需要对当前无序区(R1, R2, ……, Rn-1)
调整为新堆,然后再次将 R [1] 与无序区最后一个元素交换,得到新的无序区(R1, R2, ……, Rn-2)
和新的有序区(Rn-1, Rn)
。不断重复此过程直到有序区的元素个数为n-1
,则整个排序过程完成。
图解算法
代码实现
// 进行堆排序的主体代码
public static void heapSort(int[] arr) {
for (int i = arr.length / 2 - 1; i >= 0; i--) {
//建堆,需要非叶子结点才需要建堆
adjestSort(arr, i, arr.length);
}
for (int i = arr.length - 1; i>=0 ; i--) {
//从大顶堆中取出最大值与数组最后一位进行交换
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
//交换后可能不是大顶堆了,需要进行维护
adjestSort(arr,0,i);
}
}
//建堆及维护堆的代码
public static void adjestSort(int[] arr, int parent, int length) {
//首先先记录当前结点的值,方便后续比较
int temp = arr[parent];
//当前结点的孩子结点的下标
int Child = 2 * parent + 1;
while (Child < length){
//判断左右子树哪个比较大,取出较大值
if(Child + 1 < length && arr[Child] < arr[Child + 1]){
Child++;
}
//如果父结点大于等于子节点,那说明这是大顶堆了,并且子节点也是大顶堆,直接进行返回
if(temp >= arr[Child]){
break;
}
//走到这说明父结点肯定小于子节点了。进行替换
arr[parent] = arr[Child];
//递归到下一层再进行判断
parent = Child;
Child = 2 * parent + 1;
}
//需要将当前结点移到指定位置
arr[parent] = temp;
}
算法分析
- 稳定性:不稳定
- 时间复杂度:最佳:O(nlogn), 最差:O(nlogn), 平均:O(nlogn)
- 空间复杂度:O(1)