栈的实现
什么是栈?
栈又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。插入元素的一端称为栈顶,相对应的另一端称为栈底。
根据上一文所写LinkedList的类实现栈
public class LinkedStack<E> implements Stack<E> {
private LinkedList<E> list;
public LinkedStack() {
list = new LinkedList<E>();
}
@Override
public int getSize() {
return list.getSize();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public void push(E e) {
list.addFirst(e);
}
@Override
public E pop() {
return list.removeFirst();
}
@Override
public E peek() {
return list.getFirst();
}
@Override
public void clear() {
list.clear();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("LinkedLStack:size=" + getSize() + "\n");
if (isEmpty()) {
sb.append("[]");
} else {
sb.append('[');
for (int i = 0; i < getSize(); i++) {
sb.append(list.get(i));
if (i != getSize() - 1) {
sb.append(',');
} else {
sb.append(']');
}
}
}
return sb.toString();
}
}
实现思想:
- 在类LinkedStack的构造方法中创建LinkedList的对象list。
- getSize()方法是返回栈中元素的个数,在LinkedList已经定义size属性为元素个数,即返回list.size;
- isEmpty()方法雷同,返回list.isEmpty()方法;
- push()方法、pop()方法因为栈只能在同一端进,同一端出。如果在单向链表的链表尾插入,时间复杂度为O(1),但是从表尾出的时间复杂度为O(n);而在表头插入和删除元素的时间复杂度都为O(1)。所以从表头操作,即用list的addFirst()、removeFirst()。
- peek()是返回栈顶元素,即为list.getFirst();
- 清空方法clear()调用list.clear()即可。
队列的实现
什么是队列?
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端进行操作,而在表的后端进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端陈伟队尾,进行删除操作的端称为队头。
根据上一文所写LinkedList的类实现队列
public class LinkedQueue<E> implements Queue<E> {
private LinkedList<E> list = new LinkedList<E>();
@Override
public int getSize() {
return list.getSize();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public void clear() {
list.clear();
}
@Override
public void enqueue(E e) {
list.addLast(e);
}
@Override
public E dequeue() {
return list.removeFirst();
}
@Override
public E getFront() {
return list.getFirst();
}
@Override
public E getRear() {
return list.getLast();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("LinkedQueue:size=" + getSize() + "\n");
if (isEmpty()) {
sb.append("[]");
} else {
sb.append("[");
for (int i = 0; i < getSize(); i++) {
sb.append(list.get(i));
if (i != getSize() - 1) {
sb.append(",");
} else {
sb.append("]");
}
}
}
return sb.toString();
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (this == obj) {
return true;
}
if (obj instanceof LinkedQueue) {
LinkedQueue q = (LinkedQueue) obj;
return list.equals(q.list);
}
return false;
}
}
实现思想:
- 在类LinkedQueue中创建LinkedList的对象list。
- getSize()方法是返回栈中元素的个数,在LinkedList已经定义size属性为元素个数,即返回list.size;
- isEmpty()方法雷同,返回list.isEmpty()方法;
- enqueue()方法、dequeue()方法因为栈只能在一端进,另一端出。如果在单向链表的链表尾插入,时间复杂度为O(1),但是从表尾出的时间复杂度为O(n);而在表头插入和删除元素的时间复杂度都为O(1)。所以从表头删除,从表尾插入,即用list的addFirst()、removeFirst()。
- getFront()和getRear()方法是返回队头和队尾元素,即调用list的getFirst()和getLast()。
- 清空方法clear()调用list.clear()即可。
单向循环链表
public class LoopSingle<E> implements List<E> {
private class Node {
E data; // 数据域
Node next; // 指针域
public Node() {
this(null, null);
}
public Node(E data, Node next) {
this.data = data;
this.next = next;
}
@Override
public String toString() {
return data.toString();
}
}
private Node head;
private Node rear;
private int size;
public LoopSingle() {
head = null;
rear = null;
size = 0;
}
public LoopSingle(E[] arr) {
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
// TODO Auto-generated method stub
return size == 0 && head == null && rear == null;
}
@Override
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("插入角标非法!");
}
Node n = new Node(e, null);
if (size == 0) {
head = n;
rear = n;
rear.next = head;
} else if (index == 0) {// 头插
n.next = head;
head = n;
rear.next = n;
} else if (index == size) {// 尾插
n.next = head;
rear.next = n;
rear = n;
} else {// 一般情况
Node p = head;
for (int i = 0; i < index - 1; i++) {
p = p.next;
}
n.next = p.next;
p.next = n;
}
size++;
}
@Override
public void addFirst(E e) {
add(0, e);
}
@Override
public void addLast(E e) {
add(size, e);
}
@Override
public E get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("查找角标非法!");
}
if (index == 0) {
return head.data;
} else if (index == size - 1) {
return rear.data;
} else {
Node p = head;
for (int i = 0; i < index; i++) {
p = p.next;
}
return p.data;
}
}
@Override
public E getFirst() {
return get(0);
}
@Override
public E getLast() {
return get(size - 1);
}
@Override
public void set(int index, E e) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("修改角标非法!");
}
if (index == 0) {
head.data = e;
} else if (index == size - 1) {
rear.data = e;
} else {
Node p = head;
for (int i = 0; i < index; i++) {
p = p.next;
}
p.data = e;
}
}
@Override
public boolean contains(E e) {
return find(e) != -1;
}
@Override
public int find(E e) {
if (isEmpty()) {
return -1;
}
Node p = head;
int index = 0;
while (p.data != e) {
p = p.next;
index++;
if (p == head) {
return -1;
}
}
return index;
}
@Override
public E remove(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("删除角标非法!");
}
E res = null;
if (size == 1) {
res = head.data;
head = null;
rear = null;
} else if (index == 0) {
res = head.data;
head = head.next;
rear.next = head;
} else if (index == size - 1) {
res = rear.data;
Node p = head;
while (p.next != rear) {
p = p.next;
}
p.next = rear.next;
rear = p;
} else {
Node p = head;
for (int i = 0; i < index - 1; i++) {
p = p.next;
}
Node del = p.next;
res = del.data;
p.next = del.next;
del.next = null;
}
size--;
return res;
}
@Override
public E removeFirst() {
// TODO Auto-generated method stub
return remove(0);
}
@Override
public E removeList() {
// TODO Auto-generated method stub
return remove(size - 1);
}
@Override
public void removeElement(E e) {
remove(find(e));
}
@Override
public void clear() {
head = null;
rear = null;
size = 0;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("LoopSingle:size=" + getSize() + "\n");
if (isEmpty()) {
sb.append("[]");
} else {
sb.append('[');
Node p = head;
while (true) {
sb.append(p.data);
if (p.next == head) {
sb.append(']');
break;
} else {
sb.append(',');
}
p = p.next;
}
}
return sb.toString();
}
}
实现思想:头节点用真实节点完成。
- 当链表为空时,不存任何元素,head=null,rear=null,size=0。
- 存放第一个元素,head=n;rear=n;size=1;rear.next=head。
- 当存放第二个元素及以后的元素时,有头插法和尾插法和一般插入。
头插法:n.next = head;head = n;rear.next = n;size++;
尾插法:n.next = head;rear.next = n;rear = n;size++;
一般插入:
Node p = head;
for (int i = 0; i < index - 1; i++) {
p = p.next;
}
n.next = p.next;
p.next = n;
size++;
- 删除元素有从头删除,从尾删除和一般删除。
从头删除:res = head.data;head = head.next;rear.next = head;size–;
从尾删除:
res = rear.data;
Node p = head;
while (p.next != rear) {
p = p.next;
}
p.next = rear.next;
rear = p;
size–;
一般删除:
Node p = head;
for (int i = 0; i < index - 1; i++) {
p = p.next;
}
Node del = p.next;
res = del.data;
p.next = del.next;
del.next = null;
size–;
删除到最后一个元素时,在次删除,则head=null;rear=null,size=0。
思路分析:
- 先取出头节点的位置,p=head,数到3,则死亡,即从头节点开始,每数两个死一个,再数两个,死一个。
- 当剩余最后一个人时,再执行,直接链表为空,即head=null,rear=null,size=0。
- 当删除到头节点时,将head=head.next,rear.next =head,size–。
- 当删除到尾节点,将链表从头遍历到尾节点的上一个节点p,p.next=rear.next,rear=p,size–,p=head。
参考例图为:
上代码
/**
* 约瑟夫问题
*
* @param number人数
* @param step次序
* @return
*/
public LinkedList<Integer> JosephusLoop(int number, int step) {
if (number <= 0 || step <= 0) {
throw new IllegalArgumentException("参数不合法!");
}
head = null;
rear = null;
size = 0;
for (int i = 1; i <= number; i++) {
addLast((E) new Integer(i));
}
LinkedList<Integer> list = new LinkedList<Integer>();
Node p = head;
while (!isEmpty()) {
for (int i = 0; i < step - 2; i++) {
p = p.next;
}
Node del = p.next;
list.addLast((Integer) del.data);
if (size == 1) {
size--;
head = null;
rear = null;
break;
} else if (del == head) {
head = head.next;
rear.next = head;
size--;
p = head;
} else if (del == rear) {
p.next = rear.next;
rear = p;
size--;
p = head;
} else {
p.next = del.next;
del.next = null;
del = null;
size--;
p = p.next;
}
}
return list;
}
思路分析:
- 创建内部类Person,并向链表中存入Person对象并赋number和password。
- 取第一个人的密码为第一次的M值。
- 在约瑟夫问题的基础上,将每次删除的del节点的data传入find()方法中,找到删除节点的位置index,并将他的密码值取出,作为下一次的M值。
- 然后按照解决约瑟夫的方法完成后续内容。
上代码
/**
* 约瑟夫问题升級版
*
* @param number人数
* @param M次序
* @return 死亡順序
*/
public LinkedList<Person> JosephusLoopTwo(int number) {
if (number <= 0) {
throw new IllegalArgumentException("参数不合法!");
}
head = null;
rear = null;
size = 0;
Person person = null;
for (int i = 1; i <= number; i++) {
person = new Person(i, (int) (Math.random() * 7) + 2);// 密码生成2~9
addLast((E) person);
}
LinkedList<Person> list = new LinkedList<Person>();
Node p = head;
Person person1 = (LoopSingle<E>.Person) getFirst();
int pass = person1.password;// 第一个人的密码作为第一次死亡的号码
System.out.println("M=" + pass);
while (!isEmpty()) {
for (int i = 0; i < pass - 2; i++) {
p = p.next;
if (pass == 1) {
break;
}
}
Node del = p.next;
int count = find(del.data);// 获取删除号码的索引
person = (LoopSingle<E>.Person) get(count);
pass = person.password;// 取得删除号码的密码
list.addLast((LoopSingle<E>.Person) del.data);
if (size == 1) {
size--;
head = null;
rear = null;
break;
} else if (del == head) {
head = head.next;
rear.next = head;
size--;
p = head;
} else if (del == rear) {
p.next = rear.next;
rear = p;
size--;
p = head;
} else {
p.next = del.next;
del.next = null;
del = null;
size--;
p = p.next;
}
}
return list;
}
/**
* 约瑟夫问题升級版的人物类
*
* @author Administrator
*
*/
private class Person {
int number;
int password;
public Person() {
this(0, 0);
}
public Person(int number, int password) {
this.number = number;
this.password = password;
}
@Override
public String toString() {
return "[number=" + number + ",password=" + password + "]";
}
}
思路分析:
- 创建十三个循环链表空间,并逐个存放Integer对象为0.
- 根据扑克牌存放方法,执行完第一轮。
- 当存放扑克牌5时,进入第二轮,遇到存放牌的地方空过,不计算。
- 按照上述方法完成以后操作。
存放例图:
上代码
/**
* 扑克牌放牌顺序
*/
public void magicPocker() {
head = null;
rear = null;
size = 0;
for (int i = 0; i < 13; i++) {
addLast((E) new Integer(0));
}
Integer pockernumber = 1;
Node p = rear;
while (true) {
for (int i = 0; i < pockernumber;) {
p = p.next;
if (p.data.equals(0)) {
i++;
}
}
p.data = (E) pockernumber;
pockernumber++;
if (pockernumber.equals(14)) {
break;
}
}
System.out.println(this);
}
练习题
答案为c
解题思想:
- 此题为判定,即用==;
- 且带有头节点,所以头节点为虚拟头节点,头节点不存元素;
- 所以答案为:L->next==null
选D
访问链表中的元素必须从头节点开始遍历整个链表。
选B
和上一题相同,访问链表中的元素必须从链表的第一个节点开始遍历。
选C
二分查找法是定义头指针和尾指针,再定义一个中间的指针。
当数字大于中间指针的元素,则头指针等于中间指针+1;
当数子小于中间指针的元素,则尾指针等于中间指针-1;
更新中间指针,再次查找;
依靠上述内容,重复查找,最终找到查找元素。
选择B
链表适合执行插入和删除操作。
选择C
链表访问元素是从头遍历元素,最差的是访问最后一个元素,时间复杂度尾O(n)。
选B,注意看题。是不宜。
对顺序表来说,删除第一个元素就需要后续元素都向前移动一个位置。每删除一次都需要移动大量元素,因此不宜采用。
答案:B
线性表可以是有序的,也可以是无序的
集合与线性表的区别是是否允许元素重复
集合不允许元素重复,线性表允许元素重复
选D
链表的最大优点就是在插入或删除时不需要移动表的元素,仅仅需要修改一下指针或者实现什么链接的数据就可以了。
选A
链接线性表就是链表。
线性表的顺序存储结构和线性表的链式存储结构分别是随机存取和顺序存取。
顺序存储结构的地址在内存中是连续的,所以可以通过计算地址实现随机存取,而链式存储结构的存储地址不一定连续,只能通过第1个结点的指针顺序存取
选D
栈可以是顺序存储,也可以是链式存储,与存储结构无关。循环队列是队列的顺序存储结构,链表是线性表的链式存储结构,用散列法存储的线性表叫散列表,都与存储结构有关
1051.高度检查器
学校在拍年度纪念照时,一般要求学生按照 非递减 的高度顺序排列。
请你返回至少有多少个学生没有站在正确位置数量。该人数指的是:能让所有学生以 非递减 高度排列的必要移动人数。
示例:
输入:[1,1,4,2,1,3]
输出:3
解释:
高度为 4、3 和最后一个 1 的学生,没有站在正确的位置。
提示:
- 1 <= heights.length <= 100
- 1 <= heights[i] <= 100
解题思路:
从前往后遍历数组中的元素,当前,前面的元素大于后面的元素,则计数器++;
遍历完成后,返回计数器的值就是所求结果。
class Solution {
public int heightChecker(int[] heights) {
int[] arr = new int[101];
for (int height : heights) {
arr[height]++;
}
int count = 0;
for (int i = 1, j = 0; i < arr.length; i++) {
while (arr[i]-- > 0) {
if (heights[j++] != i) count++;
}
}
return count;
}
}