原文链接:https://blog.csdn.net/qq_23994787/article/details/77951244#_Toc9101
(1)河内塔问题: 42
(2)费式数列 43
(3)巴斯卡(Pascal)三角形 44
(4)蒙地卡罗法求 PI 45
(5)最大公因数、最小公倍数 46
(6)阿姆斯壮数 47
(7)最大访客数 48
(8)洗扑克牌(乱数排列) 49
(9)约瑟夫问题(Josephus Problem) 50
(10)排列组合 52
(11)得分排行 53
(12)选择、插入、气泡排序 55
(13)快速排序(一) 58
(14)快速排序(二) 60
(15)快速排序(三) 61
(16)合并排序 62
(17)基数排序 63
(18)循序查找法(使用卫兵) 65
(19)二分查找法 66
(20)插补查找法 67
(21)费式查找法 68
(22)稀疏矩阵 71
(23)多维矩阵转一维矩阵 72
(24)上三角、下三角、对称矩阵 73
(25)奇数魔方阵 75
(26)4N魔方阵 76
(27)2(2n+1)魔方阵 78
1.大O表示法:粗略的量度方法即算法的速度是如何与数据项的个数相关的
算法 大O表示法表示的运行时间
线性查找 O(N)
二分查找 O(logN)
无序数组的插入 O(1)
有序数组的插入 O(N)
无序数组的删除 O(N)
有序数组的删除 O(N)
O(1)是最优秀的,O(logN)良好,O(N)还可以,O(N2)稍差(在冒泡法中见到)
2. 排序
public class JWzw {
//插入排序
public void insertArray(Integer[] in ) {
int tem = 0;
int num = 0;
int upnum = 0;
for (int i = 0; i < in .length; i++) {
for (int j = i - 1; j >= 0; j--) {
num++;
if ( in [j + 1] < in [j]) {
tem = in [j + 1]; in [j + 1] = in [j]; in [j] = tem;
upnum++;
} else {
break;
}
}
}
for (int i = 0; i < in .length; i++) {
System.out.print( in [i]);
if (i < in .length - 1) {
System.out.print(",");
}
}
System.out.println();
System.out.println("插入排序循环次数:" + num);
System.out.println("移动次数:" + upnum);
System.out.print("\n\n\n");
}
//选择排序
public void chooseArray(Integer[] in ) {
int tem = 0;
int num = 0;
int upnum = 0;
for (int i = 0; i < in .length; i++) {
for (int j = 0; j < in .length - 1; j++) {
num++;
if ( in [j + 1] < in [j]) {
tem = in [j + 1]; in [j + 1] = in [j]; in [j] = tem;
upnum++;
}
}
}
for (int i = 0; i < in .length; i++) {
System.out.print( in [i]);
if (i < in .length - 1) {
System.out.print(",");
}
}
System.out.println();
System.out.println("选择排序循环次数:" + num);
System.out.println("移动次数:" + upnum);
System.out.print("\n\n\n");
}
//冒泡排序
public void efferArray(Integer[] in ) {
int tem = 0;
int num = 0;
int upnum = 0;
for (int i = 0; i < in .length; i++) {
for (int j = i; j < in .length - 1; j++) {
num++;
if ( in [j + 1] < in [i]) {
tem = in [j + 1]; in [j + 1] = in [i]; in [i] = tem;
upnum++;
}
}
}
for (int i = 0; i < in .length; i++) {
System.out.print( in [i]);
if (i < in .length - 1) {
System.out.print(",");
}
}
System.out.println();
System.out.println("冒泡排序循环次数:" + num);
System.out.println("移动次数:" + upnum);
System.out.print("\n\n\n");
}
//打印乘法口诀
public void printMulti() {
for (int j = 1; j < 10; j++) {
for (int i = 1; i <= j; i++) {
System.out.print(i + " * " + j + " = " + j * i + "\t");
}
System.out.print("\t\n");
}
System.out.print("\n\n\n");
}
//打印N * 1 + N * 2 + N * 3 =num的所有组合
public void printNumAssemble(int num) {
for (int i = 0; i < num + 1; i++) {
for (int j = 0; j < num / 2 + 1; j++) {
for (int in = 0; in < num / 3 + 1; in ++) {
if (i * 1 + j * 2 + in * 3 == num) {
System.out.println("小马" + i + ",\t中马" + j + ",\t大马" + in );
}
}
}
}
}
/**
* @param args
*/
public static void main(String[] args) {
JWzw jwzw = new JWzw();
int num = 3;
jwzw.printMulti(); //打印乘法口诀
jwzw.printNumAssemble(100); //打印N * 1 + N * 2 + N * 3 =num的所有组合
Integer in [] = {
8, 89, 5, 84, 3, 45, 12, 33, 77, 98, 456, 878, 654, 213, 897
};
jwzw.efferArray( in ); //冒泡排序
Integer in1[] = {
8, 89, 5, 84, 3, 45, 12, 33, 77, 98, 456, 878, 654, 213, 897
};
jwzw.insertArray(in1); //插入排序
Integer in2[] = {
8, 89, 5, 84, 3, 45, 12, 33, 77, 98, 456, 878, 654, 213, 897
};
jwzw.chooseArray(in2); //选择排序
//int i = num++;
//System.out.println(i);
System.out.println(1000 >> 2);
}
}
3. 优先级队列
class PriorityQueue {
private long[] a = null;
private int nItems = 0;
private int maxSize = 0;
public PriorityQueue(int maxSize) {
a = new long[maxSize];
this.maxSize = maxSize;
nItems = 0;
}
public void insert(long l) {
//优先级队列的插入不是队尾,而是选择一个合适的按照某种顺序插入的
//当队列长度为0时,如下
//不为0时,将所有比要插入的数小的数据后移,这样大的数就在队列的头部了
int i = 0;
if (nItems == 0) {
a[0] = l;
} else {
for (i = nItems - 1; i >= 0; i--) {
if (l < a[i]) a[i + 1] = a[i];
else break;
}
a[i + 1] = l;
}
nItems++;
}
public long remove() {
//移出的是数组最上端的数,这样减少数组元素的移动
return a[--nItems];
}
public boolean isEmpty() {
return (nItems == 0);
}
public boolean isFull() {
return (nItems == maxSize);
}
public int size() {
return nItems;
}
}
public class duilie { // 队列体类
private duilie s;
private String data;
duilie(String data) {
this.data = data;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public duilie getS() {
return s;
}
public void setS(duilie s) {
this.s = s;
}
}
public class duiliecz { // 队列操作类
/**
* @param args
*/
private int i = 0; // 队列长
private duilie top = new duilie(""); // 队列头
private duilie end = new duilie(""); // 队列尾
public void add(String s) { // 添加队列
duilie m = new duilie(s);
if (i != 0) {
m.setS(top.getS());
top.setS(m);
} else {
top.setS(m);
end.setS(m);
}
i++;
}
4. 队列
public void del() { // 删除队尾
if (i == 0) {
return;
} else if (i == 1) {
top.setS(null);
end.setS(null);
} else {
duilie top1 = new duilie(""); // 队列底查找用缓存
top1.setS(top.getS());
while (!top1.getS().getS().equals(end.getS())) {
top1.setS(top1.getS().getS());
}
end.setS(top1.getS());
}
i--;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
duiliecz m = new duiliecz();
m.add("1");
m.add("2");
m.add("3");
m.add("4");
for (int n = 0; n < 4; n++) {
m.del();
}
}
public int getI() {
return i;
}
public duilie getEnd() {
return end;
}
public duilie getTop() {
return top;
}
}
5. 栈
public class Stack {
int[] arr;
int len = 0;
public Stack() {
arr = new int[100];
}
public Stack(int n) {
arr = new int[n];
}
public int size() {
return len + 1;
}
// 扩大数组
public void resize() {
int[] b = new int[arr.length * 2];
System.arraycopy(arr, 0, b, 0, arr.length);
arr = b;
}
public void show() {
for (int i = 0; i < len; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
// 进栈
public void push(int a) {
if (len >= arr.length) resize();
arr[len] = a;
len++;
}
// 出栈
public int pop() {
if (len == 0) {
System.out.println();
System.out.println("stack is empty!");
return -1;
}
int a = arr[len - 1];
arr[len - 1] = 0;
len--;
return a;
}
}
6. 链表
class Node {
Object data;
Node next;
public Node(Object data) {
setData(data);
}
public void setData(Object data) {
this.data = data;
}
public Object getData() {
return data;
}
}
class Link {
Node head;
int size = 0;
public void add(Object data) {
Node n = new Node(data);
if (head == null) {
head = n;
} else {
Node current = head;
while (true) {
if (current.next == null) {
break;
}
current = current.next;
}
current.next = n;
}
size++;
}
public void show() {
Node current = head;
if (current != null) {
while (true) {
System.out.println(current);
if (current == null) {
break;
}
current = current.next;
}
} else {
System.out.println("link is empty");
}
}
public Object get(int index) {
// ....
}
public int size() {
return size;
}
}
7. 单链表
class Node // 节点类,单链表上的节点
{
String data; // 数据域,存放String类的数据
Node next; // 指向下一个节点
Node(String data) {
this.data = data; // 构造函数
}
String get() {
return data; // 返回数据
}
}
class MyLinkList // 链表类
{
Node first; // 头节点
int size; // 链表长度
MyLinkList(String arg[]) {
// Node first = new Node("head");//生成头节点
first = new Node("head"); // J.F. 这里不需要定义局部变量 first
// 如果定义了局部变量,那成员变量 first 就一直没有用上
// 所以,它一直为空
size = 0;
Node p = first;
for (int i = 0; i < arg.length; i++) // 将arg数组中的元素分别放入链表中
{
Node q = new Node(arg[i]);
q.next = p.next; // 每一个节点存放一个arg数组中的元素
p.next = q;
p = p.next;
size++;
}
}
MyLinkList() // 无参数构造函数
{
// Node first = new Node("head");
first = new Node("head"); // J.F. 这里犯了和上一个构造方法同样的错误
size = 0;
}
int size() // 返回链表长度
{
return size;
}
void insert(Node a, int index) // 将节点a 插入链表中的第index个位置
{
Node temp = first;
for (int i = 0; i < index; i++) {
temp = temp.next; // 找到插入节点的前一节点
}
a.next = temp.next; // 插入节点
temp.next = a;
size++;
}
Node del(int index) // 删除第index个节点,并返回该值
{
Node temp = first;
for (int i = 0; i < index; i++) {
temp = temp.next; // 找到被删除节点的前一节点
}
Node node = temp.next;
temp.next = node.next;
size--; // 删除该节点,链表长度减一
return node;
}
void print() // 在屏幕上输出该链表(这段程序总是出错,不知道错在哪里)
{
Node temp = first;
for (int i = 1; i < size; i++) // 将各个节点分别在屏幕上输出
{
temp = temp.next;
System.out.print(temp.get() + "->");
}
}
void reverse() // 倒置该链表
{
for (int i = 0; i < size; i++) {
insert(del(size - 1), 0); // 将最后一个节点插入到最前
// J.F. 最后一个节点的 index 应该是 size - 1
// 因为第一个节点的 index 是 0
}
}
String get(int index) // 查找第index个节点,返回其值
{
if (index >= size) {
return null;
}
Node temp = first;
for (int i = 0; i < index; i++) {
temp = temp.next; // 找到被查找节点的前一节点
}
return temp.next.get();
}
}
class MyStack // 堆栈类,用单链表实现
{
MyLinkList tmp;
Node temp;
MyStack() {
// MyLinkList tmp = new MyLinkList();
tmp = new MyLinkList(); // J.F. 和 MyLinkList 构造方法同样的错误
}
void push(String a) // 压栈,即往链表首部插入一个节点
{
Node temp = new Node(a);
tmp.insert(temp, 0);
}
String pop() // 出栈,将链表第一个节点删除
{
Node a = tmp.del(0);
return a.get();
}
int size() {
return tmp.size();
}
boolean empty() // 判断堆栈是否为空
{
if (tmp.size() == 0) return false;
else return true;
}
}
public class MyLinkListTest // 测试程序部分
{
public static void main(String arg[]) // 程序入口
{
if ((arg.length == 0) || (arg.length > 10)) System.out.println("长度超过限制或者缺少参数");
else {
MyLinkList ll = new MyLinkList(arg); // 创建一个链表
ll.print(); // 先输出该链表(运行到这一步抛出异常)
ll.reverse(); // 倒置该链表
ll.print(); // 再输出倒置后的链表
String data[] = new String[10];
int i;
for (i = 0; i < ll.size(); i++) {
data[i] = ll.get(i); // 将链表中的数据放入数组
}
// sort(data);// 按升序排列data中的数据(有没有现成的排序函数?)
for (i = 0; i < ll.size(); i++) {
System.out.print(data[i] + ";"); // 输出数组中元素
}
System.out.println();
MyStack s = new MyStack(); // 创建堆栈实例s
for (i = 0; i < ll.size(); i++) {
s.push(data[i]); // 将数组元素压栈
}
while (!s.empty()) {
System.out.print(s.pop() + ";"); // 再将堆栈里的元素弹出
}
}
}
}
8. 双端链表
class Link {
public int iData = 0;
public Link next = null;
public Link(int iData) {
this.iData = iData;
}
public void display() {
System.out.print(iData + " ");
}
}
class FirstLastList {
private Link first = null;
private Link last = null;
public FirstLastList() {
first = null;
last = null;
}
public void insertFirst(int key) {
Link newLink = new Link(key);
if (this.isEmpty()) last = newLink;
newLink.next = first;
first = newLink;
}
public void insertLast(int key) {
Link newLink = new Link(key);
if (this.isEmpty()) first = newLink;
else last.next = newLink;
last = newLink;
}
public Link deleteFirst() {
Link temp = first;
if (first.next == null) last = null;
first = first.next;
return temp;
}
public boolean isEmpty() {
return (first == null);
}
public void displayList() {
System.out.print("List (first-->last): ");
Link current = first;
while (current != null) {
current.display();
current = current.next;
}
System.out.println("");
}
}
class FirstLastListApp {
public static void main(String[] args) {
// TODO Auto-generated method stub
FirstLastList theList = new FirstLastList();
theList.insertFirst(22); // insert at front
theList.insertFirst(44);
theList.insertFirst(66);
theList.insertLast(11); // insert at rear
theList.insertLast(33);
theList.insertLast(55);
theList.displayList(); // display the list
theList.deleteFirst(); // delete first two items
theList.deleteFirst();
theList.displayList(); // display again
}
}
9. 有序链表
package arithmetic;
class Link {
public int iData = 0;
public Link next = null;
public Link(int iData) {
this.iData = iData;
}
public void display() {
System.out.print(iData + " ");
}
}
class SortedList {
private Link first = null;
public SortedList() {
first = null;
}
public void insert(int key) {
Link newLink = new Link(key);
Link previous = null;
Link current = first;
// while的第一个条件是没有到达链表的尾端,第二个是按顺序找到一个合适的位置
while (current != null && key > current.iData) {
previous = current;
current = current.next;
}
// 如果是空表或者要插入的元素最小,则在表头插入key
if (current == first) first = newLink;
else previous.next = newLink;
newLink.next = current;
}
/**
* 删除表头的节点
*
* @return 要删除的节点
*/
public Link remove() {
Link temp = first;
first = first.next;
return temp;
}
public boolean isEmpty() {
return (first == null);
}
public void displayList() {
System.out.print("List (first-->last): ");
Link current = first; // start at beginning of list
while (current != null) // until end of list,
{
current.display(); // print data
current = current.next; // move to next link
}
System.out.println("");
}
}
class SortedListApp {
public static void main(String[] args) { // create new list
SortedList theSortedList = new SortedList();
theSortedList.insert(20); // insert 2 items
theSortedList.insert(40);
theSortedList.displayList(); // display list
theSortedList.insert(10); // insert 3 more items
theSortedList.insert(30);
theSortedList.insert(50);
theSortedList.displayList(); // display list
theSortedList.remove(); // remove an item
theSortedList.displayList(); // display list
}
}
10. 双向链表
class Link {
// 双向链表,有两个指针,一个向前,一个向后
public int iData = 0;
public Link previous = null;
public Link next = null;
public Link(int iData) {
this.iData = iData;
}
public void display() {
System.out.print(iData + " ");
}
}
class DoublyLinked {
// 分别指向链表的表头和表尾
private Link first = null;
private Link last = null;
public boolean isEmpty() {
return first == null;
}
/**
* 在表头插入数据
*
* @param 要插入的节点的数据
*/
public void insertFirst(int key) {
Link newLink = new Link(key);
// 如果开始链表为空,则插入第一个数据后,last也指向第一个数据
if (this.isEmpty()) last = newLink;
else { // 表不为空的情况
first.previous = newLink;
newLink.next = first;
}
// 无论怎样,插入后都的让first重新指向第一个节点
first = newLink;
}
public void insertLast(int key) { // 在尾端插入数据,同上
Link newLink = new Link(key);
if (this.isEmpty()) first = newLink;
else {
last.next = newLink;
newLink.previous = last;
}
last = newLink;
}
/**
* 在指定的节点后插入数据
*
* @param key
* 指定的节点的值
* @param iData
* 要插入的数据
* @return 是否插入成功
*/
public boolean insertAfter(int key, int iData) {
Link newLink = new Link(key);
Link current = first;
// 从first开始遍历,看能否找到以key为关键字的节点
while (current.iData != key) {
current = current.next;
// 若能找到就跳出循环,否则返回false,插入失败
if (current == null) return false;
}
// 如果插入点在last的位置
if (current == last) {
last = newLink;
} else { // 非last位置,交换各个next和previous的指针
newLink.next = current.next;
current.next.previous = newLink;
}
current.next = newLink;
newLink.previous = current;
return true;
}
/**
* 删除表头的节点
*
* @return
*/
public Link deleteFirst() {
Link temp = first;
// 如果表中只有一个元素,删除后则为空表,所以last=null
if (first.next == null) last = null;
else
// 否则,让第二个元素的previous=null
first.next.previous = null;
// 删除头指针,则first指向原来的second
first = first.next;
return temp;
}
public Link deleteLast() { // 同上
Link temp = last;
if (last.previous == null) first = null;
else last.previous.next = null;
last = last.previous;
return temp;
}
public Link deleteKey(int key) {
Link current = first;
// 遍历整个链表查找对应的key,如果查到跳出循环,否则...
while (current.iData != key) {
current = current.next;
// ...否则遍历到表尾,说明不存在此key,返回null,删除失败
if (current == null) return null;
}
if (current == first) first = first.next;
else current.previous.next = current.next;
if (current == last) last = last.previous;
else current.next.previous = current.previous;
return current;
}
public void displayForward() {
Link current = first;
while (current != null) {
current.display();
current = current.next;
}
System.out.println();
}
public void displayBackward() {
Link current = last;
while (current != null) {
current.display();
current = current.previous;
}
System.out.println();
}
}
class DoublyLinkedApp {
public static void main(String[] args) { // make a new list
DoublyLinked theList = new DoublyLinked();
theList.insertFirst(22); // insert at front
theList.insertFirst(44);
theList.insertFirst(66);
theList.insertLast(11); // insert at rear
theList.insertLast(33);
theList.insertLast(55);
theList.displayForward(); // display list forward
theList.displayBackward(); // display list backward
theList.deleteFirst(); // delete first item
theList.deleteLast(); // delete last item
theList.deleteKey(11); // delete item with key 11
theList.displayForward(); // display list forward
theList.insertAfter(22, 77); // insert 77 after 22
theList.insertAfter(33, 88); // insert 88 after 33
theList.displayForward(); // display list forward
}
}
11. 实现二叉树前序遍历迭代器
class TreeNode这个类用来声明树的结点,其中有左子树、右子树和自身的内容。
class MyTree这个类用来声明一棵树,传入根结点。这里设计的比较简单
class TreeEum这个类是树的迭代器,通过 MyTree类的方法获取,这里主要就是设计它了。代码如下:
//TreeNode类,使用了泛型,由于比较简单,考试.大提示不作解释
class TreeNode < E > {
E node;
private TreeNode < String > left;
private TreeNode < String > right;
public TreeNode(E e) {
this(e, null, null);
}
public TreeNode(E e, TreeNode < String > left, TreeNode < String > right) {
this.node = e;
this.left = left;
this.right = right;
}
public TreeNode < String > left() {
return left;
}
public TreeNode < String > right() {
return right;
}
}
// MyTree类,没什么功能,传入根结点构造,getEnumerator()方法获取迭代器。
class MyTree {
TreeNode < String > root;
public MyTree(TreeNode < String > root) {
this.root = root;
}
public TreeEnum getEnumerator() {
return new TreeEnum(root);
}
}
// 这个类为迭代器,有详细解释,相信各位能看懂。在栈中用了两次泛型。
import java.util.Stack;
public class TreeEnum {
private TreeNode < String > root;
private Stack < TreeNode < String >> store; /* 保存遍历左子树但未遍历右子树的结点 */
private TreeNode < String > next;
public TreeEnum(TreeNode < String > root) {
this.root = root;
store = new Stack < TreeNode < String >> ();
next = root;
}
public TreeNode < String > next() {
TreeNode < String > current = next;
if (next != null) {
/* 如果当前结点的左子树不为空,则遍历左子树,并标记当前结点未遍历右子树 */
if (next.left() != null) {
store.push(next);
next = next.left();
}
// 如果当前结点的左子树为空,则遍历右子树
else if (next.right() != null) {
next = next.right();
}
/* 如果当前结点为叶子,则找未遍历右子树的结点并且遍历它的右子树 */
else {
if (!store.empty()) /* 判断是否还有结点的右子树未遍历 */ {
TreeNode < String > tmp = store.pop();
/* 如果有未遍历右子树的结点,但它的右子树为空,且还有结点的右子树未遍历, */
/* 则一直往上取,直到取到未遍历右子树且右子树不为空的结点,遍历它的右子树. */
while ((tmp.right() == null) && !store.empty()) {
tmp = store.pop();
}
next = tmp.right();
}
else {
/* 如果没有哪个结点右子树未遍历,则表示没有下一个结点了,设置next为null */
next = null;
}
}
}
return current;
}
public boolean hasMoreElement() {
return next != null;
}
} 下面写个测试类,不作解释,相信大家看得懂
public class TreeReader {
public static void main(String[] args) {
TreeNode < String > n1 = new TreeNode < String > ("n1");
TreeNode < String > n2 = new TreeNode < String > ("n2");
TreeNode < String > n3 = new TreeNode < String > ("n3");
TreeNode < String > n4 = new TreeNode < String > ("n4");
TreeNode < String > n5 = new TreeNode < String > ("n5");
TreeNode < String > n6 = new TreeNode < String > ("n6", null, n1);
TreeNode < String > n7 = new TreeNode < String > ("n7", n2, null);
TreeNode < String > n8 = new TreeNode < String > ("n8", n7, null);
TreeNode < String > n9 = new TreeNode < String > ("n9", null, n5);
TreeNode < String > n10 = new TreeNode < String > ("n10", n4, n9);
TreeNode < String > n11 = new TreeNode < String > ("n11", n6, n8);
TreeNode < String > n12 = new TreeNode < String > ("n12", n3, n10);
TreeNode < String > root = new TreeNode < String > ("root", n11, n12);
MyTree tree = new MyTree(root);
TreeEnum eum = tree.getEnumerator();
while (eum.hasMoreElement()) {
System.out.print(eum.next().node + "--");
}
System.out.println("end");
}
}
12. 迭代器
package TreeIterator;
public interface Iterator {
public boolean hasNext();
public Object next();
}这个接口我们有
2个方法, hasNext()是否还有下一条数据, next返回具体的 Object这里也就是树。我们先不必要忙着做他的实现类,我们现在要来做的是这个容器(不是 JAVA中容器,与 arraylist什么的无关),正所谓树的容器是什么,是山也!我们想想山应该具有什么呢!?首先它要有种植树的功能,这里可以看作添加树。我们可以想像山的功能是和树相互关联的,那么他们之间是什么关系呢,我们给他们一种聚合的关系,聚合的关系大家可以参考 UML图,我在这里给出它的一种程序表现形式。
package TreeIterator;
public class Hall {
Tree[] tree; // 这里可以看作是聚合关系
private int index; // 指向Tree[]的标签
public Hall(int maxNumber) {
tree = new Tree[maxNumber];
index = 0;
}
public void add(Tree tree) {
this.tree[index] = tree;
index++;
}
public Iterator connectIterator() {
return new TreeIterator(this);
}
}
这里我们定义的山可以抽象出
Hall类来, Tree[] tree可以看作是山和树之间的一种聚合关系。 add方法就是添加树。问题来了,山和树有了关系,那么山和迭代器有什么关系呢。它们之间肯定有一种关系。我们有了这个容器(山),就要把这个容器来实现迭代的方法: hasNext()和 Next().恩这里我们可以看出,山和迭代器之间也是一种关联关系。我们就把它看成是一种聚合关系(TIP:聚合关系一种特殊的关联关系)。我们可以通过一个 connectIterator方法来链接山和迭代器,接下来我们要去做一个具体的迭代类,这个具体的类中间有了 hasNext()和 Next()的具体实现方法
package TreeIterator;
public class TreeIterator implements Iterator {
private int last = 0;
private Hall hall;
public TreeIterator(Hall hall) {
this.hall = hall;
}
public boolean hasNext() {
if (last < hall.tree.length) return true;
else return false;
}
public Tree next() {
Tree t = hall.tree[last];
last++;
return t;
}
}
这里Hall hall就可以看作是一种关联关系,我们要把山和迭代器联系起来就通过构造函数来实现, hasNext和 next实现方法就体现出来了有了山,有了迭代器,可是树还没有定义,不过这个树的方法还很好解决!树不关联其他的事务,我们可以简单的这么写:
package TreeIterator;
public class Tree {
private String name;
public Tree(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
好了似乎我们整个工程完工了,我们现在来模拟一下农民老大伯来种树撒肥的过程;
package TreeIterator;
public class Pren {
public Pren() {}
public static void main(String[] args) {
Hall hall = new Hall(4);
hall.add(new Tree("苹果树"));
hall.add(new Tree("梨树"));
hall.add(new Tree("橘子树"));
hall.add(new Tree("凤梨树"));
for (Iterator i = hall.connectIterator(); i.hasNext();) {
String Type = ((Tree)(i.next())).getName();
if (Type == "苹果树") {
System.out.println("洒苹果树的农药,");
}
if (Type == "梨树") {
System.out.println("洒梨树的农药");
}
if (Type == "橘子树") {
System.out.println("洒橘子树的农药,洒了也没用,还没到成熟日,现在没结果实");
}
if (Type == "凤梨树") {
System.out.println("南风天,湿气大,让它烂在地里吧");
}
}
}
}
种4个树,山小而五脏俱全,更像一个土包,再要有树才行啊,所以 4个树的实例出现。好了接下来种树,几毫秒解决!山了有我们就要把山放到迭代器中间去了。遍历这个山(容器)。联想我们看看 arrayList中的迭代器模式是怎么实现的!
ArrayList a = new ArrayList();
a.add("a1");
a.add("a2");
a.add("a3");
a.add("a4");
for (Iterator i = a.iterator(); i.hasNext();) {
System.out.println(i.next().toString());
}
13. 合并搜索算法
public class MergeSortArray {
private long[] theArray;
private int nElems;
public MergeSortArray(int max) {
theArray = new long[max];
nElems = 0;
}
public void insert(long value) {
theArray[nElems] = value; // insert it
nElems++; // increment size
}
public void display() {
for (int j = 0; j < nElems; j++) System.out.print(theArray[j] + " ");
System.out.println("");
}
public void mergeSort() {
long[] workSpace = new long[nElems];
recMergeSort(workSpace, 0, nElems - 1);
}
private void recMergeSort(long[] workSpace, int lowerBound, int upperBound) {
if (lowerBound == upperBound) // if range is 1,
return; // no use sorting
else { // find midpoint
int mid = (lowerBound + upperBound) / 2;
// sort low half
recMergeSort(workSpace, lowerBound, mid);
// sort high half
recMergeSort(workSpace, mid + 1, upperBound);
// merge them
merge(workSpace, lowerBound, mid + 1, upperBound);
}
}
private void merge(long[] workSpace, int lowPtr, int highPtr, int upperBound) {
int j = 0; // workspace index
int lowerBound = lowPtr;
int mid = highPtr - 1;
int n = upperBound - lowerBound + 1; // # of items
while (lowPtr <= mid && highPtr <= upperBound)
if (theArray[lowPtr] < theArray[highPtr]) workSpace[j++] = theArray[lowPtr++];
else workSpace[j++] = theArray[highPtr++];
while (lowPtr <= mid) workSpace[j++] = theArray[lowPtr++];
while (highPtr <= upperBound) workSpace[j++] = theArray[highPtr++];
for (j = 0; j < n; j++) theArray[lowerBound + j] = workSpace[j];
}
public static void main(String[] args) {
int maxSize = 100; // array size
MergeSortArray arr = new MergeSortArray(maxSize); // create the array
arr.insert(14);
arr.insert(21);
arr.insert(43);
arr.insert(50);
arr.insert(62);
arr.insert(75);
arr.insert(14);
arr.insert(2);
arr.insert(39);
arr.insert(5);
arr.insert(608);
arr.insert(36);
arr.display();
arr.mergeSort();
arr.display();
}
}
14. 递归
public class Recursion {
public static void main(String[] args) {
// TODO Auto-generated method stub
Recursion re = new Recursion();
System.out.println(re.RecursionNum(10));
}
public int RecursionNum(int num) {
if (num > 0) {
return num + RecursionNum(num - 1);
}
Else {
return 0;
}
}
}
15. 归并排序
/**
* 归并排序,要求待排序的数组必须实现Comparable接口
*/
public class MergeSort implements SortStrategy {
private Comparable[] bridge;
/**
* 利用归并排序算法对数组obj进行排序
*/
public void sort(Comparable[] obj) {
if (obj == null) {
throw new NullPointerException("The param can not be null!");
}
bridge = new Comparable[obj.length]; // 初始化中间数组
mergeSort(obj, 0, obj.length - 1); // 归并排序
bridge = null;
}
/**
* 将下标从left到right的数组进行归并排序
*
* @param obj
* 要排序的数组的句柄
* @param left
* 要排序的数组的第一个元素下标
* @param right
* 要排序的数组的最后一个元素的下标
*/
private void mergeSort(Comparable[] obj, int left, int right) {
if (left < right) {
int center = (left + right) / 2;
mergeSort(obj, left, center);
mergeSort(obj, center + 1, right);
merge(obj, left, center, right);
}
}
/**
* 将两个对象数组进行归并,并使归并后为升序。归并前两个数组分别有序
*
* @param obj
* 对象数组的句柄
* @param left
* 左数组的第一个元素的下标
* @param center
* 左数组的最后一个元素的下标
* @param right
* 右数组的最后一个元素的下标
*/
private void merge(Comparable[] obj, int left, int center, int right) {
int mid = center + 1;
int third = left;
int tmp = left;
while (left <= center && mid <= right) { // 从两个数组中取出小的放入中间数组
if (obj[left].compareTo(obj[mid]) <= 0) {
bridge[third++] = obj[left++];
} else bridge[third++] = obj[mid++];
}
// 剩余部分依次置入中间数组
while (mid <= right) {
bridge[third++] = obj[mid++];
}
while (left <= center) {
bridge[third++] = obj[left++];
}
// 将中间数组的内容拷贝回原数组
copy(obj, tmp, right);
}
/**
* 将中间数组bridge中的内容拷贝到原数组中
*
* @param obj
* 原数组的句柄
* @param left
* 要拷贝的第一个元素的下标
* @param right
* 要拷贝的最后一个元素的下标
*/
private void copy(Comparable[] obj, int left, int right) {
while (left <= right) {
obj[left] = bridge[left];
left++;
}
}
}
16. 希尔排序
间隔序列:
h = 3 * h + 1, h = (h - 1) / 3
public class ShellSort {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
ShellSort ss = new ShellSort();
int num[] = {
546, 87, 21, 3124, 65, 2, 9, 3, 213, 54, 98, 23, 6, 4, 7,
8, 123, 872, 61, 5, 8954
};
ss.shellArray(num);
for (int i = 0; i < num.length; i++) {
System.out.println(num[i]);
}
}
public void shellArray(int[] num) {
int i = 1;
int tem, in ;
for (; i < num.length / 3;) {
i = 3 * i + 1;
}
for (; i >= 1;) {
for (int j = i; j < num.length; j++) {
tem = num[j]; in = j;
while ( in > i - 1 && num[ in -i] >= tem) {
num[ in ] = num[ in -i]; in = in -i;
}
num[ in ] = tem;
}
i = (i - 1) / 3;
}
}
}
17. 快速排序
class QuickSort {
private int[] data;
QuickSort(int[] data) {
this.data = data;
}
public void quickSort() {
recQuickSort(data, 0, data.length - 1);
}
private void recQuickSort(int[] data, int low, int high) {
// 设置两个滑标
int lowCursor = low + 1;
int highCursor = high;
// 交换时的临时变量
int temp = 0;
// 比较枢值,设为数组的第一个值
int medi = data[low];
while (true) {
// 从低端开始查找,确定大于数 data[low] 所在的位置
while (lowCursor < high && data[lowCursor] < medi) {
lowCursor++;
}
// 从高端开始查找,确定小于数 data[low] 所在的位置。这里要使用 >= 判断确定小于值
while (highCursor > low && data[highCursor] >= medi) {
highCursor--;
}
// 两游标位置出现越界,退出循环
if (lowCursor >= highCursor) {
break;
}
// 交换 data[highCursor] 和 data[lowCursor] 位置数据
temp = data[highCursor];
data[highCursor] = data[lowCursor];
data[lowCursor] = temp;
}
// 由 while 循环退出条件可知:lowCursor > highCursor
// 当前 lowCursor 指向右侧大于 data[low]的第一个位置;
// 而 highCursor 指向左侧小于 data[low]的第一个位置,所以需要交换 data[low] 和
// data[highCursor]的值
data[low] = data[highCursor];
data[highCursor] = medi;
// 递归运算左半部分
if (low < highCursor) {
recQuickSort(data, low, highCursor);
}
// 递归运算右半部分
if (lowCursor < high) {
recQuickSort(data, lowCursor, high);
}
}
public void display() {
for (int i = 0; i < data.length; i++) {
System.out.print(data[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int[] data = new int[] {
43, 12, 32, 55, 33, 67, 54, 65, 43, 22, 66,
98, 74
};
QuickSort sort = new QuickSort(data);
sort.display();
sort.quickSort();
sort.display();
}
}
18. 二叉树
//******************************************************************************************************//
//*****本程序包括简单的二叉树类的实现和前序,中序,后序,层次遍历二叉树算法,*******//
//******以及确定二叉树的高度,制定对象在树中的所处层次以及将树中的左右***********//
//******孩子节点对换位置,返回叶子节点个数删除叶子节点,并输出所删除的叶子节点**//
//*******************************CopyRight By phoenix*******************************************//
//************************************Jan 12,2008*************************************************//
//****************************************************************************************************//
public class BinTree {
public final static int MAX = 40;
BinTree[] elements = new BinTree[MAX]; // 层次遍历时保存各个节点
int front; // 层次遍历时队首
int rear; // 层次遍历时队尾
private Object data; // 数据元数
private BinTree left, right; // 指向左,右孩子结点的链
public BinTree() {}
public BinTree(Object data) { // 构造有值结点
this.data = data;
left = right = null;
}
public BinTree(Object data, BinTree left, BinTree right) { // 构造有值结点
this.data = data;
this.left = left;
this.right = right;
}
public String toString() {
return data.toString();
}
// 前序遍历二叉树
public static void preOrder(BinTree parent) {
if (parent == null) return;
System.out.print(parent.data + " ");
preOrder(parent.left);
preOrder(parent.right);
}
// 中序遍历二叉树
public void inOrder(BinTree parent) {
if (parent == null) return;
inOrder(parent.left);
System.out.print(parent.data + " ");
inOrder(parent.right);
}
// 后序遍历二叉树
public void postOrder(BinTree parent) {
if (parent == null) return;
postOrder(parent.left);
postOrder(parent.right);
System.out.print(parent.data + " ");
}
// 层次遍历二叉树
public void LayerOrder(BinTree parent) {
elements[0] = parent;
front = 0;
rear = 1;
while (front < rear) {
try {
if (elements[front].data != null) {
System.out.print(elements[front].data + " ");
if (elements[front].left != null) elements[rear++] = elements[front].left;
if (elements[front].right != null) elements[rear++] = elements[front].right;
front++;
}
} catch (Exception e) {
break;
}
}
}
// 返回树的叶节点个数
public int leaves() {
if (this == null) return 0;
if (left == null && right == null) return 1;
return (left == null ? 0 : left.leaves()) + (right == null ? 0 : right.leaves());
}
// 结果返回树的高度
public int height() {
int heightOfTree;
if (this == null) return -1;
int leftHeight = (left == null ? 0 : left.height());
int rightHeight = (right == null ? 0 : right.height());
heightOfTree = leftHeight < rightHeight ? rightHeight : leftHeight;
return 1 + heightOfTree;
}
// 如果对象不在树中,结果返回-1;否则结果返回该对象在树中所处的层次,规定根节点为第一层
public int level(Object object) {
int levelInTree;
if (this == null) return -1;
if (object == data) return 1; // 规定根节点为第一层
int leftLevel = (left == null ? -1 : left.level(object));
int rightLevel = (right == null ? -1 : right.level(object));
if (leftLevel < 0 && rightLevel < 0) return -1;
levelInTree = leftLevel < rightLevel ? rightLevel : leftLevel;
return 1 + levelInTree;
}
// 将树中的每个节点的孩子对换位置
public void reflect() {
if (this == null) return;
if (left != null) left.reflect();
if (right != null) right.reflect();
BinTree temp = left;
left = right;
right = temp;
}
// 将树中的所有节点移走,并输出移走的节点
public void defoliate() {
if (this == null) return;
// 若本节点是叶节点,则将其移走
if (left == null && right == null) {
System.out.print(this + " ");
data = null;
return;
}
// 移走左子树若其存在
if (left != null) {
left.defoliate();
left = null;
}
// 移走本节点,放在中间表示中跟移走...
// innerNode += this + " ";
data = null;
// 移走右子树若其存在
if (right != null) {
right.defoliate();
right = null;
}
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
BinTree e = new BinTree("E");
BinTree g = new BinTree("G");
BinTree h = new BinTree("H");
BinTree i = new BinTree("I");
BinTree d = new BinTree("D", null, g);
BinTree f = new BinTree("F", h, i);
BinTree b = new BinTree("B", d, e);
BinTree c = new BinTree("C", f, null);
BinTree tree = new BinTree("A", b, c);
System.out.println("前序遍历二叉树结果: ");
tree.preOrder(tree);
System.out.println();
System.out.println("中序遍历二叉树结果: ");
tree.inOrder(tree);
System.out.println();
System.out.println("后序遍历二叉树结果: ");
tree.postOrder(tree);
System.out.println();
System.out.println("层次遍历二叉树结果: ");
tree.LayerOrder(tree);
System.out.println();
System.out.println("F所在的层次: " + tree.level("F"));
System.out.println("这棵二叉树的高度: " + tree.height());
System.out.println("--------------------------------------");
tree.reflect();
System.out.println("交换每个节点的孩子节点后......");
System.out.println("前序遍历二叉树结果: ");
tree.preOrder(tree);
System.out.println();
System.out.println("中序遍历二叉树结果: ");
tree.inOrder(tree);
System.out.println();
System.out.println("后序遍历二叉树结果: ");
tree.postOrder(tree);
System.out.println();
System.out.println("层次遍历二叉树结果: ");
tree.LayerOrder(tree);
System.out.println();
System.out.println("F所在的层次: " + tree.level("F"));
System.out.println("这棵二叉树的高度: " + tree.height());
}
}
说明:
河内之塔(Towers of Hanoi)是法国人M.Claus(Lucas)于1883年从泰国带至法国的,河内为越战时北越的首都,即现在的胡志明市;1883年法国数学家 Edouard Lucas曾提及这个故事,据说创世纪时Benares有一座波罗教塔,是由三支钻石棒(Pag)所支撑,开始时神在第一根棒上放置64个由上至下依由小至大排列的金盘(Disc),并命令僧侣将所有的金盘从第一根石棒移至第三根石棒,且搬运过程中遵守大盘子在小盘子之下的原则,若每日仅搬一个盘子,则当盘子全数搬运完毕之时,此塔将毁损,而也就是世界末日来临之时。
解法:
如果柱子标为ABC,要由A搬至C,在只有一个盘子时,就将它直接搬至C,当有两个盘子,就将B当作辅助柱。
如图所示:
事实上,若有n个盘子,则移动完毕所需之次数为2^n - 1,所以当盘数为64时,则所需次数为:264- 1 = 18446744073709551615 为5.05390248594782e+16年,也就是约5000世纪,如果对这数字没什么概念,就假设每秒钟搬一个盘子好了,也要约5850亿年左右。
实现:
//Java程序的实现
import java.io.*;
public class Hanoi {
public static void main(String args[]) throws IOException {
int n;
BufferedReader buf;
buf = new BufferedReader(new InputStreamReader(System. in ));
System.out.print("请输入盘数:");
n = Integer.parseInt(buf.readLine());
Hanoi hanoi = new Hanoi();
hanoi.move(n, 'A', 'B', 'C');
}
public void move(int n, char a, char b, char c) {
if (n == 1) System.out.println("盘 " + n + " 由 " + a + " 移至 " + c);
else {
move(n - 1, a, c, b);
System.out.println("盘 " + n + " 由 " + a + " 移至 " + c);
move(n - 1, b, a, c);
}
}
}
说明:
Fibonacci为1200年代的欧洲数学家,在他的著作中曾经提到:“若有一只免子每个月生一只小免子,一个月后小免子也开始生产。起初只有一只免子,一个月后就有两只免子,二个月后有三只免子,三个月后有五只免子(小免子投入生产)......”。
如果不太理解这个例子的话,举个图就知道了,注意新生的小免子需一个月成长期才会投入生产,类似的道理也可以用于植物的生长,这就是Fibonacci数列,一般习惯称之为费氏数列,例如以下:
1、1 、2、3、5、8、13、21、34、55、89......
解法:
依说明,我们可以将费氏数列定义为以下:
fn = fn-1 + fn-2 if n > 2
fn = 1 if n = 0, 1
实现:
//Java程序的实现:
public class Fibonacci {
public static void main(String[] args) {
int[] fib = new int[20];
fib[0] = 0;
fib[1] = 1;
for (int i = 2; i < fib.length; i++) fib[i] = fib[i - 1] + fib[i - 2];
for (int i = 0; i < fib.length; i++) System.out.print(fib[i] + " ");
System.out.println();
}
}
说明:
巴斯卡(Pascal)三角形基本上就是在解 nCr ,因为三角形上的每一个数字各对应一个nCr,其中 n 为 row,而 r 为 column,如下:
0C0
1C0 1C1
2C0 2C1 2C2
3C0 3C1 3C2 3C3
4C0 4C1 4C2 4C3 4C4
对应的数据如下图所示:
解法:
巴斯卡三角形中的 nCr 可以使用以下这个公式来计算,以避免阶乘运算时的数值溢位:
nCr = [(n-r+1)*nCr-1]/r
nC0 = 1
实现:
//java实现
import java.awt.*;
import javax.swing.*;
public class Pascal extends JFrame {
public Pascal() {
setBackground(Color.white);
setTitle("巴斯卡三角形");
setSize(520, 350);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(700, 700);
setVisible(true);
}
private long combi(int n, int r) {
int i;
long p = 1;
for (i = 1; i <= r; i++) p = p * (n - i + 1) / i;
return p;
}
public void paint(Graphics g) {
g.setColor(Color.white);
g.clearRect(0, 0, getSize().width, getSize().height);
g.setColor(Color.red);
final int N = 12;
int n, r, t;
for (n = 0; n <= N; n++) {
for (r = 0; r <= n; r++) g.drawString(" " + combi(n, r), (N - n) * 20 + r * 40, n * 20 + 50);
}
}
public static void main(String args[]) {
Pascal frm = new Pascal();
}
}
说明:
蒙地卡罗为摩洛哥王国之首都,该国位于法国与义大利国境,以赌博闻名。蒙地卡罗的基本原理为以乱数配合面积公式来进行解题,这种以机率来解题的方式带有赌博的意味,虽然在精确度上有所疑虑,但其解题的思考方向却是个值得学习的方式。
解法:
蒙地卡罗的解法适用于与面积有关的题目,例如求PI值或椭圆面积,这边介绍如何求PI值;假设有一个圆半径为1,所以四分之一圆面积就为PI,而包括此四分之一圆的正方形面积就为1,如下图所示:
如果随意的在正方形中投射飞标(点)好了,则这些飞标(点)有些会落于四分之一圆内,假设所投射的飞标(点)有n点,在圆内的飞标(点)有c点,则依比例来算,就会得到上图中最后的公式。
至于如何判断所产生的点落于圆内,很简单,令乱数产生X与Y两个数值,如果X^2+Y^2等于1就是落在圆内。
实现:
//java程序实现
public class PI {
public static void main(String[] args) {
final int N = 50000;
int sum = 0;
for (int i = 1; i < N; i++) {
double x = Math.random();
double y = Math.random();
if ((x * x + y * y) < 1) sum++;
}
System.out.println("PI = " + (double) 4 * sum / N);
}
}
说明:
解法:
最大公因数使用辗转相除法来求,最小公倍数则由这个公式来求:
GCD * LCM = 两数乘积
实现:
//java程序实现
import java.io.*;
public class GcdLcm {
public static int gcdOf(int m, int n) {
int r;
while (n != 0) {
r = m % n;
m = n;
n = r;
}
return m;
}
public static int lcmOf(int m, int n) {
return m * n / gcdOf(m, n);
}
public static void main(String[] args) throws IOException {
BufferedReader ln = new BufferedReader(new InputStreamReader(System. in ));
System.out.print("请输入第一个数:");
int x = Integer.parseInt(ln.readLine());
System.out.print("请输入第二个数:");
int y = Integer.parseInt(ln.readLine());
System.out.println("GCD of (" + x + "," + y + ")=" + GcdLcm.gcdOf(x, y));
System.out.println("LCM of (" + x + "," + y + ")=" + GcdLcm.lcmOf(x, y));
}
}
说明:
在三位的整数中,例如153可以满足13 + 53 + 33 = 153,这样的数称之为Armstrong数,试写出一程式找出所有的三位数Armstrong数。
解法:
Armstrong数的寻找,其实就是在问如何将一个数字分解为个位数、十位数、百位数......,这只要使用除法与余数运算就可以了,例如输入 input为abc,则:
a = input / 100
b = (input%100) / 10
c = input % 10
实现:
//java程序实现
public class Armstrong {
public static void main(String[] args) {
System.out.println("寻找Armstrong数:");
for (int i = 100; i <= 999; i++) {
int a = i / 100;
int b = (i % 100) / 10;
int c = i % 10;
if (a * a * a + b * b * b + c * c * c == i) System.out.print(i + " ");
}
System.out.println();
}
}
说明:
现将举行一个餐会,让访客事先填写到达时间与离开时间,为了掌握座位的数目,必须先估计不同时间的最大访客数。
解法:
这个题目看似有些复杂,其实相当简单,单就计算访客数这个目的,同时考虑同一访客的来访时间与离开时间,反而会使程式变得复杂;只要将来访时间与离开时间分开处理就可以了,假设访客 i 的来访时间为x[i],而离开时间为y[i]。
在资料输入完毕之后,将x[i]与y[i]分别进行排序(由小到大),道理很简单,只要先计算某时之前总共来访了多少访客,然后再减去某时之前的离开访客,就可以轻易的解出这个问题
实现:
//java实现
import java.io.*;
import java.util.*;
public class MaxVisit {
public static int maxGuest(int[] x, int[] y, int time) {
int num = 0;
for (int i = 0; i < x.length; i++) {
if (time > x[i]) num++;
if (time > y[i]) num--;
}
return num;
}
public static void main(String[] args) throws IOException {
BufferedReader buf = new BufferedReader(new InputStreamReader(System. in ));
System.out.println("输入来访时间与离开时间(0~24):");
System.out.println("范例:10 15");
System.out.println("输入-1结束");
java.util.ArrayList list = new ArrayList();
while (true) {
System.out.print(">>");
String input = buf.readLine();
if (input.equals("-1")) break;
list.add(input);
}
int[] x = new int[list.size()];
int[] y = new int[list.size()];
for (int i = 0; i < x.length; i++) {
String input = (String) list.get(i);
String[] strs = input.split(" ");
x[i] = Integer.parseInt(strs[0]);
y[i] = Integer.parseInt(strs[1]);
}
Arrays.sort(x);
Arrays.sort(y);
for (int time = 0; time < 25; time++) {
System.out.println(time + " 时的最大访客数:" + MaxVisit.maxGuest(x, y, time));
}
}
}
说明:
洗扑克牌的原理其实与乱数排列是相同的,都是将一组数字(例如1~N)打乱重新排列,只不过洗扑克牌多了一个花色判断的动作而已。
解法:
初学者通常会直接想到,随机产生1~N的乱数并将之存入阵列中,后来产生的乱数存入阵列前必须先检查阵列中是否已有重复的数字,如果有这个数就不存入,再重新产生下一个数,运气不好的话,重复的次数就会很多,程式的执行速度就很慢了,这不是一个好方法。
以1~52的乱数排列为例好了,可以将阵列先依序由1到52填入,然后使用一个回圈走访阵列,并随机产生1~52的乱数,将产生的乱数当作索引取出阵列值,并与目前阵列走访到的值相交换,如此就不用担心乱数重复的问题了,阵列走访完毕后,所有的数字也就重新排列了。
至于如何判断花色?这只是除法的问题而已,取商数判断花色,取余数判断数字,您可以直接看程式比较清楚。
实现:
//java实现
public class ShuffleCard {
public static void main(String args[]) {
final int N = 52;
int[] poker = new int[N + 1];
// 初始化阵列
for (int i = 1; i <= N; i++) poker[i] = i;
// 洗牌
for (int i = 1; i <= N; i++) {
int j = (int)(Math.random() * N);
if (j == 0) j = 1;
int tmp = poker[i];
poker[i] = poker[j];
poker[j] = tmp;
}
for (int i = 1; i <= N; i++) {
// 判断花色
switch ((poker[i] - 1) / 13) {
case 0:
System.out.print("桃");
break;
case 1:
System.out.print("心");
break;
case 2:
System.out.print("砖");
break;
case 3:
System.out.print("梅");
break;
}
// 扑克牌数字
int remain = poker[i] % 13;
switch (remain) {
case 0:
System.out.print("K ");
break;
case 12:
System.out.print("Q ");
break;
case 11:
System.out.print("J ");
break;
default:
System.out.print(remain + " ");
break;
}
if (i % 13 == 0) System.out.println("");
}
}
}
说明:
据说着名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人 开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。
然而Josephus 和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
解法:
约瑟夫问题可用代数分析来求解,将这个问题扩大好了,假设现在您与m个朋友不幸参与了这个游戏,您要如何保护您与您的朋友?只要画两个圆圈就可以让自己与朋友免于死亡游戏,这两个圆圈内圈是排列顺序,而外圈是自杀顺序,如下图所示:
使用程式来求解的话,只要将阵列当作环状来处理就可以了,在阵列中由计数1开始,每找到三个无资料区就填入一个计数,直而计数达41为止,然后将阵列由索引1开始列出,就可以得知每个位置的自杀顺序,这就是约瑟夫排列,41个人而报数3的约琴夫排列如下所示:
14 36 1 38 15 2 24 30 3 16 34 4 25 17 5 40 31 6 18 26 7 37 19 8 35 27 9 20 32 10 41 21 11 28 39 12 22 33 13 29 23
由上可知,最后一个自杀的是在第31个位置,而倒数第二个自杀的要排在第16个位置,之前的人都死光了,所以他们也就不知道约琴夫与他的朋友并没有遵守游戏规则了。
实现:
//java实现
public class Josephus {
public static int[] arrayOfJosephus(int number, int per) {
int[] man = new int[number];
for (int count = 1, i = 0, pos = -1; count <= number; count++) {
do {
pos = (pos + 1) % number; // 环状处理
if (man[pos] == 0) i++;
if (i == per) { // 报数为3了
i = 0;
break;
}
} while (true);
man[pos] = count;
}
return man;
}
public static void main(String[] args) {
int[] man = Josephus.arrayOfJosephus(41, 3);
int alive = 3;
System.out.println("约琴夫排列:");
for (int i = 0; i < 41; i++) System.out.print(man[i] + " ");
System.out.println("\nL表示3个存活的人要放的位置:");
for (int i = 0; i < 41; i++) {
if (man[i] > (41 - alive)) System.out.print("L");
else System.out.print("D");
if ((i + 1) % 5 == 0) System.out.print(" ");
}
System.out.println();
}
}
说明:
将一组数字、字母或符号进行排列,以得到不同的组合顺序,例如1 2 3这三个数的排列组合有:1 2 3、1 3 2、2 1 3、2 3 1、3 1 2、3 2 1。
解法:
可以使用递回将问题切割为较小的单元进行排列组合,例如1 2 3 4的排列可以分为1 [2 3 4]、2 [1 3 4]、3 [1 2 4]、4 [1 2 3]进行排列,这边利用旋转法,先将旋转间隔设为0,将最右边的数字旋转至最左边,并逐步增加旋转的间隔,例如:
1 2 3 4 -> 旋转1 -> 继续将右边2 3 4进行递回处理
2 1 3 4 -> 旋转1 2 变为 2 1-> 继续将右边1 3 4进行递回处理
3 1 2 4 -> 旋转1 2 3变为 3 1 2 -> 继续将右边1 2 4进行递回处理
4 1 2 3 -> 旋转1 2 3 4变为4 1 2 3 -> 继续将右边1 2 3进行递回处理
实现:
//java实现
public class Permutation {
public static void perm(int[] num, int i) {
if (i < num.length - 1) {
for (int j = i; j <= num.length - 1; j++) {
int tmp = num[j];
// 旋转该区段最右边数字至最左边
for (int k = j; k > i; k--) num[k] = num[k - 1];
num[i] = tmp;
perm(num, i + 1);
// 还原
for (int k = i; k < j; k++) num[k] = num[k + 1];
num[j] = tmp;
}
} else {
// 显示此次排列
for (int j = 1; j <= num.length - 1; j++) System.out.print(num[j] + " ");
System.out.println();
}
}
public static void main(String[] args) {
int[] num = new int[4 + 1];
for (int i = 1; i <= num.length - 1; i++) num[i] = i;
perm(num, 1);
}
}
说明:
假设有一教师依学生座号输入考试分数,现希望在输入完毕后自动显示学生分数的排行,当然学生的分数可能相同。
解法:
这个问题基本上要解不难,只要使用额外的一个排行阵列走访分数阵列就可以了,直接使用下面的程式片段作说明:
for(i = 0; i < count; i++) {
juni[i] = 1;
for(j = 0; j < count; j++) {
if(score[j] > score[i])
juni[i]++;
}
}
printf("得分\t排行\n");
for(i = 0; i < count; i++)
printf("%d\t%d\n", score[i], juni[i]);
上面这个方法虽然简单,但是反覆计算的次数是n^2,如果n值变大,那么运算的时间就会拖长;改变juni阵列的长度为n+2,并将初始值设定为0,如下所示:
接下来走访分数阵列,并在分数所对应的排行阵列索引元素上加1,如下所示:
将排行阵列最右边的元素设定为1,然后依序将右边的元素值加至左边一个元素,最后排行阵列中的「分数+1」」就是得该分数的排行,如下所示:
这样的方式看起来复杂,其实不过在计算某分数之前排行的人数,假设89分之前的排行人数为x人,则89分自然就是x+1了,这也是为什么排行阵列最右边要设定为1的原因;如果89分有y人,则88分自然就是x+y+1,整个阵列右边元素向左加的原因正是如此。
如果分数有负分的情况,由于C/C++或Java等程式语言无法处理负的索引,所以必须加上一个偏移值,将所有的分数先往右偏移一个范围即可,最后显示的时候记得减回偏移值就可以了。
实现:
//
import java.io.*;
public class ScoreRank {
public static void main(String[] args)
throws NumberFormatException, IOException {
final int MAX = 100;
final int MIN = 0;
int[] score = new int[MAX + 1];
int[] juni = new int[MAX + 2];
BufferedReader reader = new BufferedReader(new InputStreamReader(System. in ));
int count = 0;
do {
System.out.print("输入分数,-1结束:");
score[count++] = Integer.parseInt(reader.readLine());
} while ((score[count - 1] != -1));
count--;
for (int i = 0; i < count; i++) juni[score[i]]++;
juni[MAX + 1] = 1;
for (int i = MAX; i >= MIN; i--) juni[i] = juni[i] + juni[i + 1];
System.out.println("得分\t排行");
for (int i = 0; i < count; i++) {
System.out.println(score[i] + "\t" + juni[score[i] + 1]);
}
}
}
说明:
选择排序(Selection sort)、插入排序(Insertion sort)与气泡排序(Bubble sort)这三个排序方式是初学排序所必须知道的三个基本排序方式,它们由于速度不快而不实用(平均与最快的时间复杂度都是O(n2)),然而它们排序的方式确是值得观察与探讨的。
解法:
① 选择排序
将要排序的对象分作两部份,一个是已排序的,一个是未排序的,从后端未排序部份选择一个最小值,并放入前端已排序部份的最后一个,例如:
排序前:70 80 31 37 10 1 48 60 33 80
[1] 80 31 37 10 70 48 60 33 80 选出最小值1
[1 10] 31 37 80 70 48 60 33 80 选出最小值10
[1 10 31] 37 80 70 48 60 33 80 选出最小值31
[1 10 31 33] 80 70 48 60 37 80 ......
[1 10 31 33 37] 70 48 60 80 80 ......
[1 10 31 33 37 48] 70 60 80 80 ......
[1 10 31 33 37 48 60] 70 80 80 ......
[1 10 31 33 37 48 60 70] 80 80 ......
[1 10 31 33 37 48 60 70 80] 80 ......
② 插入排序
像是玩朴克一样,我们将牌分作两堆,每次从后面一堆的牌抽出最前端的牌,然后插入前面一堆牌的适当位置,例如:
排序前:92 77 67 8 6 84 55 85 43 67
[77 92] 67 8 6 84 55 85 43 67 将77插入92前
[67 77 92] 8 6 84 55 85 43 67 将67插入77前
[8 67 77 92] 6 84 55 85 43 67 将8插入67前
[6 8 67 77 92] 84 55 85 43 67 将6插入8前
[6 8 67 77 84 92] 55 85 43 67 将84插入92前
[6 8 55 67 77 84 92] 85 43 67 将55插入67前
[6 8 55 67 77 84 85 92] 43 67 ......
[6 8 43 55 67 77 84 85 92] 67 ......
[6 8 43 55 67 67 77 84 85 92] ......
③ 气泡排序法
顾名思义,就是排序时,最大的元素会如同气泡一样移至右端,其利用比较相邻元素的方法,将大的元素交换至右端,所以大的元素会不断的往右移动,直到适当的位置为止。
基本的气泡排序法可以利用旗标的方式稍微减少一些比较的时间,当寻访完阵列后都没有发生任何的交换动作,表示排序已经完成,而无需再进行之后的回圈比较与交换动作,例如:
排序前:95 27 90 49 80 58 6 9 18 50
27 90 49 80 58 6 9 18 50 [95] 95浮出
27 49 80 58 6 9 18 50 [90 95] 90浮出
27 49 58 6 9 18 50 [80 90 95] 80浮出
27 49 6 9 18 50 [58 80 90 95] ......
27 6 9 18 49 [50 58 80 90 95] ......
6 9 18 27 [49 50 58 80 90 95] ......
6 9 18 [27 49 50 58 80 90 95] 由于接下来不会再发生交换动作,排序提早结束
在上面的例子当中,还加入了一个观念,就是当进行至i与i+1时没有交换的动作,表示接下来的i+2至n已经排序完毕,这也增进了气泡排序的效率。
实现:
//Java程序实现
public class BasicSort {
public static void selectionSort(int[] number) {
for (int i = 0; i < number.length - 1; i++) {》》
int m = i;
for (int j = i + 1; j < number.length; j++)
if (number[j] < number[m]) m = j; === = if (i != m) swap(number, i, m);
}
}
public static void injectionSort(int[] number) {
for (int j = 1; j < number.length; j++) {
int tmp = number[j];
int i = j - 1;
while (tmp < number[i]) {
number[i + 1] = number[i];
i--;
if (i == -1) break;
}
number[i + 1] = tmp;
}
}
public static void bubbleSort(int[] number) {
boolean flag = true;
for (int i = 0; i < number.length - 1 && flag; i++) {
flag = false;
for (int j = 0; j < number.length - i - 1; j++) {
if (number[j + 1] < number[j]) {
swap(number, j + 1, j);
flag = true;
}
}
}
}
private static void swap(int[] number, int i, int j) {
int t;
t = number[i];
number[i] = number[j];
number[j] = t;
}
public static void main(String[] args) {
//测试:
int[] a = {
10, 9, 1, 100, 20, 200, 39, 45, 23, 18, 2, 2, 15
};
//测试选择排序:
System.out.println("选择排序前:");
for (int x: a) System.out.print(x + " ");
System.out.println();
int[] b = new int[a.length];
b = a;
selectionSort(b);
System.out.println("选择排序后:");
for (int x: b) System.out.print(x + " ");
System.out.println();
//测试插入排序:
System.out.println("插入排序前:");
for (int x: a) System.out.print(x + " ");
System.out.println();
int[] c = new int[a.length];
c = a;
injectionSort(c);
System.out.println("插入排序后:");
for (int x: c) System.out.print(x + " ");
System.out.println();
//测试气泡排序:
System.out.println("气泡排序前:");
for (int x: a) System.out.print(x + " ");
System.out.println();
int[] d = new int[a.length];
d = a;
bubbleSort(d);
System.out.println("气泡排序后:");
for (int x: d) System.out.print(x + " ");
}
}
说明:
快速排序法(quick sort)是目前所公认最快的排序方法之一(视解题的对象而定),虽然快速排序法在最差状况下可以达O(n2),但是在多数的情况下,快速排序法的效率表现是相当不错的。
快速排序法的基本精神是在数列中找出适当的轴心,然后将数列一分为二,分别对左边与右边数列进行排序,而影响快速排序法效率的正是轴心的选择。
这边所介绍的第一个快速排序法版本,是在多数的教科书上所提及的版本,因为它最容易理解,也最符合轴心分割与左右进行排序的概念,适合对初学者进行讲解。
解法:
这边所介绍的快速演算如下:将最左边的数设定为轴,并记录其值为 s
廻圈处理:
令索引 i 从数列左方往右方找,直到找到大于 s 的数
令索引 j 从数列左右方往左方找,直到找到小于 s 的数
如果 i >= j,则离开回圈
如果 i < j,则交换索引i与j两处的值
将左侧的轴与 j 进行交换
对轴左边进行递回
对轴右边进行递回
透过以下演算法,则轴左边的值都会小于s,轴右边的值都会大于s,如此再对轴左右两边进行递回,就可以对完成排序的目的,例如下面的实例,*表示要交换的数,[]表示轴:
[41] 24 76* 11 45 64 21 69 19 36*
[41] 24 36 11 45* 64 21 69 19* 76
[41] 24 36 11 19 64* 21* 69 45 76
[41] 24 36 11 19 21 64 69 45 76
21 24 36 11 19 [41] 64 69 45 76
在上面的例子中,41左边的值都比它小,而右边的值都比它大,如此左右再进行递回至排序完成。
实现:
//java实现
public class QuickSort {
public static void sort(int[] number) {
sort(number, 0, number.length - 1);
}
private static void sort(int[] number, int left, int right) {
if (left < right) {
int s = number[left];
int i = left;
int j = right + 1;
while (true) {
// 向右找
while (i + 1 < number.length && number[++i] < s);
// 向左找
while (j - 1 > -1 && number[--j] > s);
if (i >= j) break;
swap(number, i, j);
}
number[left] = number[j];
number[j] = s;
sort(number, left, j - 1);
// 对左边进行递回
sort(number, j + 1, right);
// 对右边进行递回
}
}
private static void swap(int[] number, int i, int j) {
int t;
t = number[i];
number[i] = number[j];
number[j] = t;
}
}
说明:
在快速排序法(一)中,每次将最左边的元素设为轴,而之前曾经说过,快速排序法的加速在于轴的选择,在这个例子中,只将轴设定为中间的元素,依这个元素作基准进行比较,这可以增加快速排序法的效率。
解法:
在这个例子中,取中间的元素s作比较,同样的先得右找比s大的索引 i,然后找比s小的索引 j,只要两边的索引还没有交会,就交换 i 与 j 的元素值,这次不用再进行轴的交换了,因为在寻找交换的过程中,轴位置的元素也会参与交换的动作,例如:
41 24 76 11 45 64 21 69 19 36
首先left为0,right为9,(left+right)/2 = 4(取整数的商),所以轴为索引4的位置,比较的元素是45,您往右找比45大的,往左找比45小的进行交换:
41 24 76* 11 [45] 64 21 69 19 *36
41 24 36 11 45* 64 21 69 19* 76
41 24 36 11 19 64* 21* 69 45 76
[41 24 36 11 19 21] [64 69 45 76]
完成以上之后,再初别对左边括号与右边括号的部份进行递回,如此就可以完成排序的目的。
实现:
public class QuickSort {
public static void sort(int[] number) {
sort(number, 0, number.length - 1);
}
private static void sort(int[] number, int left, int right) {
if (left < right) {
int s = number[(left + right) / 2];
int i = left - 1;
int j = right + 1;
while (true) {
// 向右找
while (number[++i] < s);
// 向左找
while (number[--j] > s);
if (i >= j) break;
swap(number, i, j);
}
sort(number, left, i - 1);
// 对左边进行递回
sort(number, j + 1, right);
// 对右边进行递回
}
}
private static void swap(int[] number, int i, int j) {
int t;
t = number[i];
number[i] = number[j];
number[j] = t;
}
}
说明:
之前说过轴的选择是快速排序法的效率关键之一,在这边的快速排序法的轴选择方式更加快了快速排序法的效率,它是来自演算法名书 Introduction to Algorithms 之中。
解法:
先说明这个快速排序法的概念,它以最右边的值s作比较的标准,将整个数列分为三个部份,一个是小于s的部份,一个是大于s的部份,一个是未处理的部份,如下所示 :
在排序的过程中,i 与 j 都会不断的往右进行比较与交换,最后数列会变为以下的状态:
然后将s的值置于中间,接下来就以相同的步骤会左右两边的数列进行排序的动作,如下所示:
整个演算的过程,直接摘录书中的虚拟码来作说明:
实现:
public class QuickSort3 {
public static void sort(int[] number) {
sort(number, 0, number.length - 1);
}
private static void sort(int[] number, int left, int right) {
if (left < right) {
int q = partition(number, left, right);
sort(number, left, q - 1);
sort(number, q + 1, right);
}
}
private static int partition(int number[], int left, int right) {
int s = number[right];
int i = left - 1;
for (int j = left; j < right; j++) {
if (number[j] <= s) {
i++;
swap(number, i, j);
}
}
swap(number, i + 1, right);
return i + 1;
}
private static void swap(int[] number, int i, int j) {
int t;
t = number[i];
number[i] = number[j];
number[j] = t;
}
}
说明:
之前所介绍的排序法都是在同一个阵列中的排序,考虑今日有两笔或两笔以上的资料,它可能是不同阵列中的资料,或是不同档案中的资料,如何为它们进行排序?
解法:
可以使用合并排序法,合并排序法基本是将两笔已排序的资料合并并进行排序,如果所读入的资料尚未排序,可以先利用其它的排序方式来处理这两笔资料,然后再将排序好的这两笔资料合并。
有人问道,如果两笔资料本身就无排序顺序,何不将所有的资料读入,再一次进行排序?排序的精神是尽量利用资料已排序的部份,来加快排序的效率,小笔资料的排序较为快速,如果小笔资料排序完成之后,再合并处理时,因为两笔资料都有排序了,所有在合并排序时会比单纯读入所有的资料再一次排序来的有效率。
那么可不可以直接使用合并排序法本身来处理整个排序的动作?而不动用到其它的排序方式?答案是肯定的,只要将所有的数字不断的分为两个等分,直到最后剩一个数字为止,然后再反过来不断的合并,就如下图所示:
不过基本上分割又会花去额外的时间,不如使用其它较好的排序法来排序小笔资料,再使用合并排序来的有效率。
实现:
public class MergeSort {
public static int[] sort(int[] number1, int[] number2) {
int[] number3 = new int[number1.length + number2.length];
int i = 0, j = 0, k = 0;
while (i < number1.length && j < number2.length) {
if (number1[i] <= number2[j]) number3[k++] = number1[i++];
else number3[k++] = number2[j++];
}
while (i < number1.length) number3[k++] = number1[i++];
while (j < number2.length) number3[k++] = number2[j++];
return number3;
}
}
说明:
在之前所介绍过的排序方法,都是属于「比较性」的排序法,也就是每次排序时 ,都是比较整个键值的大小以进行排序。
这边所要介绍的「基数排序法」(radix sort)则是属于「分配式排序」(distribution sort),基数排序法又称「桶子法」(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些「桶」中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的比较性排序法。
解法:
基数排序的方式可以采用LSD(Least sgnificant digital)或MSD(Most sgnificant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。
以LSD为例,假设原来有一串数值如下所示:
73, 22, 93, 43, 55, 14, 28, 65, 39, 81
首先根据个位数的数值,在走访数值时将它们分配至编号0到9的桶子中:
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
|
81 |
|
|
|
65 |
|
|
|
39 |
|
|
|
43 |
14 |
55 |
|
|
28 |
|
|
|
|
93 |
|
|
|
|
|
|
|
|
22 |
73 |
|
|
|
|
|
|
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
81, 22, 73, 93, 43, 14, 55, 65, 28, 39
接着再进行一次分配,这次是根据十位数来分配:
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
|
28 |
39 |
|
|
|
|
|
|
|
|
14 |
22 |
|
43 |
55 |
65 |
73 |
81 |
93 |
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
14, 22, 28, 39, 43, 55, 65, 73, 81, 93
这时候整个数列已经排序完毕;如果排序的对象有三位数以上,则持续进行以上的动作直至最高位数为止。
LSD的基数排序适用于位数小的数列,如果位数多的话,使用MSD的效率会比较好,MSD的方式恰与LSD相反,是由高位数为基底开始进行分配,其他的演 算方式则都相同。
实现:
public class RadixSort {
public static void sort(int[] number, int d) {
int k = 0;
int n = 1;
int[][] temp = new int[number.length][number.length];
int[] order = new int[number.length];
while (n <= d) {
for (int i = 0; i < number.length; i++) {
int lsd = ((number[i] / n) % 10);
temp[lsd][order[lsd]] = number[i];
order[lsd]++;
}
for (int i = 0; i < number.length; i++) {
if (order[i] != 0)
for (int j = 0; j < order[i]; j++) {
number[k] = temp[i][j];
k++;
}
order[i] = 0;
}
n *= 10;
k = 0;
}
}
public static void main(String[] args) {
int[] data = {
73, 22, 93, 43, 55, 14, 28, 65, 39, 81, 33, 100
};
RadixSort.sort(data, 100);
for (int i = 0; i < data.length; i++) {
System.out.print(data[i] + " ");
}
}
}
说明:
搜寻的目的,是在「已排序的资料」中寻找指定的资料,而当中循序搜寻是最基本的搜寻法,只要从资料开头寻找到最后,看看是否找到资料即可。
解法:
初学者看到循序搜寻,多数都会使用以下的方式来进行搜寻:
while(i < MAX) {
if(number[i] == k) {
printf("找到指定值");
break;
}
i++;
}
这个方法基本上没有错,但是可以加以改善,可以利用设定卫兵的方式,省去if判断式,卫兵通常设定在数列最后或是最前方,假设设定在列前方好了(索引0的 位置),我们从数列后方向前找,如果找到指定的资料时,其索引值不是0,表示在数列走访完之前就找到了,在程式的撰写上,只要使用一个while回圈就可 以了。
实现:
public class LinearSearch {
public static int search(int[] number, int des) {
int[] tmp = new int[number.length + 1];
for (int i = 1; i < tmp.length; i++) {
tmp[i] = number[i - 1];
}
tmp[0] = des;
int k = tmp[0];
int i = number.length;
while (tmp[i] != k) i--;
return i - 1;
}
public static void main(String[] args) {
int[] number = {
1, 4, 2, 6, 7, 3, 9, 8
};
QuickSort.sort(number);
int find = LinearSearch.search(number, 3);
if (find != 0) System.out.println("找到数值于索引" + find);
else System.out.println("找不到数值");
}
}
说明:
如果搜寻的数列已经有排序,应该尽量利用它们已排序的特性,以减少搜寻比对的次数,这是搜寻的基本原则,二分搜寻法是这个基本原则的代表。
解法:
在二分搜寻法中,从数列的中间开始搜寻,如果这个数小于我们所搜寻的数,由于数列已排序,则该数左边的数一定都小于要搜寻的对象,所以无需浪费时间在左边的数;如果搜寻的数大于所搜寻的对象,则右边的数无需再搜寻,直接搜寻左边的数。
所以在二分搜寻法中,将数列不断的分为两个部份,每次从分割的部份中取中间数比对,例如要搜寻92于以下的数列,首先中间数索引为(0+9)/2 = 4(索引由0开始):
[3 24 57 57 67 68 83 90 92 95]
由于67小于92,所以转搜寻右边的数列:
3 24 57 57 67 [68 83 90 92 95]
由于90小于92,再搜寻右边的数列,这次就找到所要的数了:
3 24 57 57 67 68 83 90 [92 95]
实现:
public class BinarySearch {
public static int search(int[] number, int des) {
int low = 0;
int upper = number.length - 1;
while (low <= upper) {
int mid = (low + upper) / 2;
if (number[mid] < des) low = mid + 1;
else if (number[mid] > des) upper = mid - 1;
else return mid;
}
return -1;
}
public static void main(String[] args) {
int[] number = {
1, 4, 2, 6, 7, 3, 9, 8
};
QuickSort.sort(number);
int find = BinarySearch.search(number, 3);
if (find != -1) System.out.println("找到数值于索引" + find);
else System.out.println("找不到数值");
}
}
说明:
如果却搜寻的资料分布平均的话,可以使用插补(Interpolation)搜寻法来进行搜寻,在搜寻的对象大于500时,插补搜寻法会比 二分搜寻法 来的快速。
解法:
插补搜寻法是以资料分布的近似直线来作比例运算,以求出中间的索引并进行资料比对,如果取出的值小于要寻找的值,则提高下界,如果取出的值大于要寻找的值,则降低下界,如此不断的减少搜寻的范围,所以其本原则与二分搜寻法是相同的,至于中间值的寻找是透过比例运算,如下所示,其中K是指定要寻找的对象, 而m则是可能的索引值:
实现:
public class InterpolationSearch {
public static int search(int[] number, int des) {
int low = 0;
int upper = number.length - 1;
while (low <= upper) {
int mid = (upper - low) * (des - number[low]) / (number[upper] - number[low]) + low;
if (mid < low || mid > upper) return -1;
if (des < number[mid]) upper = mid - 1;
else if (des > number[mid]) low = mid + 1;
else return mid;
}
return -1;
}
public static void main(String[] args) {
int[] number = {
1, 4, 2, 6, 7, 3, 9, 8
};
QuickSort.sort(number);
int find = InterpolationSearch.search(number, 3);
if (find != -1) System.out.println("找到数值于索引" + find);
else System.out.println("找不到数值");
}
}
说明:
二分搜寻法每次搜寻时,都会将搜寻区间分为一半,所以其搜寻时间为O(log(2)n),log(2)表示以2为底的log值,这边要介绍的费氏搜寻,其利用费氏数列作为间隔来搜寻下一个数,所以区间收敛的速度更快,搜寻时间为O(logn)。
解法:
费氏搜寻使用费氏数列来决定下一个数的搜寻位置,所以必须先制作费氏数列,这在之前有提过;费氏搜寻会先透过公式计算求出第一个要搜寻数的位置,以及其代表的费氏数,以搜寻对象10个数字来说,第一个费氏数经计算后一定是F5,而第一个要搜寻的位置有两个可能,例如若在下面的数列搜寻的话(为了计算方便, 通常会将索引0订作无限小的数,而数列由索引1开始):
-infin; 1 3 5 7 9 13 15 17 19 20
如果要搜寻5的话,则由索引F5 = 5开始搜寻,接下来如果数列中的数小于指定搜寻值时,就往左找,大于时就向右,每次找的间隔是F4、F3、F2来寻找,当费氏数为0时还没找到,就表示寻找失败,如下所示:
由于第一个搜寻值索引F5 = 5处的值小于19,所以此时必须对齐数列右方,也就是将第一个搜寻值的索引改为F5+2 = 7,然后如同上述的方式进行搜寻,如下所示:
至于第一个搜寻值是如何找到的?我们可以由以下这个公式来求得,其中n为搜寻对象的个数:
Fx + m = n
Fx <= n
也就是说Fx必须找到不大于n的费氏数,以10个搜寻对象来说:
Fx + m = 10
取Fx = 8, m = 2,所以我们可以对照费氏数列得x = 6,然而第一个数的可能位置之一并不是F6,而是第x-1的费氏数,也就是F5 = 5。
如果数列number在索引5处的值小于指定的搜寻值,则第一个搜寻位置就是索引5的位置,如果大于指定的搜寻值,则第一个搜寻位置必须加上m,也就是F5 + m = 5 + 2 = 7,也就是索引7的位置,其实加上m的原因,是为了要让下一个搜寻值刚好是数列的最后一个位置。
费氏搜寻看来难懂,但只要掌握Fx + m = n这个公式,自己找几个实例算一次,很容易就可以理解;费氏搜寻除了收敛快速之外,由于其本身只会使用到加法与减法,在运算上也可以加快。
实现:
public class FibonacciSearch {
public static int search(int[] number, int des) {
int[] fib = createFibonacci(number.length);
int x = findX(fib, number.length + 1, des);
int m = number.length - fib[x];
x--;
int i = x;
if (number[i] < des) i += m;
while (fib[x] > 0) {
if (number[i] < des) i += fib[--x];
else if (number[i] > des) i -= fib[--x];
else return i;
}
return -1;
}
private static int[] createFibonacci(int max) {
int[] fib = new int[max];
for (int i = 0; i < fib.length; i++) {
fib[i] = Integer.MIN_VALUE;
}
fib[0] = 0;
fib[1] = 1;
for (int i = 2; i < max; i++) fib[i] = fib[i - 1] + fib[i - 2];
return fib;
}
private static int findX(int[] fib, int n, int des) {
int i = 0;
while (fib[i] <= n) i++;
i--;
return i;
}
public static void main(String[] args) {
int[] number = {
1, 4, 2, 6, 7, 3, 9, 8
};
QuickSort.sort(number);
int find = FibonacciSearch.search(number, 3);
if (find != -1) System.out.println("找到数值于索引" + find);
else System.out.println("找不到数值");
}
}
说明:
如果在矩阵中,多数的元素并没有资料,称此矩阵为稀疏矩阵(sparse matrix),由于矩阵在程式中常使用二维阵列表示,二维阵列的大小与使用的记忆体空间成正比,如果多数的元素没有资料,则会造成记忆体空间的浪费,为 此,必须设计稀疏矩阵的阵列储存方式,利用较少的记忆体空间储存完整的矩阵资讯。
解法:
在这边所介绍的方法较为简单,阵列只储存矩阵的行数、列数与有资料的索引位置及其值,在需要使用矩阵资料时,再透过程式运算加以还原,例如若矩阵资料如下,其中0表示矩阵中该位置没有资料:
0 0 0 0 0 0
0 3 0 0 0 0
0 0 0 6 0 0
0 0 9 0 0 0
0 0 0 0 12 0
这个矩阵是5X6矩阵,非零元素有4个,您要使用的阵列第一列记录其列数、行数与非零元素个数:
5 6 4
阵列的第二列起,记录其位置的列索引、行索引与储存值:
1 1 3
2 3 6
3 2 9
4 4 12
所以原本要用30个元素储存的矩阵资讯,现在只使用了15个元素来储存,节省了不少记忆体的使用。
实现:
public class SparseMatrix {
public static int[][] restore(int[][] sparse) {
int row = sparse[0][0];
int column = sparse[0][1];
int[][] array = new int[row][column];
int k = 1;
for (int i = 0; i < row; i++) {
for (int j = 0; j < column; j++) {
if (k <= sparse[0][2] && i == sparse[k][0] && j == sparse[k][1]) {
array[i][j] = sparse[k][2];
k++;
} else array[i][j] = 0;
}
}
return array;
}
public static void main(String[] args) {
int[][] sparse = {
{
5, 6, 4
}, {
1, 1, 3
}, {
2, 3, 6
}, {
3, 2, 9
}, {
4, 4, 12
}
};
int[][] array = SparseMatrix.restore(sparse);
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array[i].length; j++) {
System.out.print(array[i][j] + " ");
}
System.out.println();
}
}
}
说明:
有的时候,为了运算方便或资料储存的空间问题,使用一维阵列会比二维或多维阵列来得方便,例如上三角矩阵、下三角矩阵或对角矩阵,使用一维阵列会比使用二维阵列来得节省空间。
解法:
以二维阵列转一维阵列为例,索引值由0开始,在由二维阵列转一维阵列时,我们有两种方式:「以列(Row)为主」或「以行(Column)为主」。由于 C/C++、Java等的记忆体配置方式都是以列为主,所以您可能会比较熟悉前者(Fortran的记忆体配置方式是以行为主)。
以列为主的二维阵列要转为一维阵列时,是将二维阵列由上往下一列一列读入一维阵列,此时索引的对应公式如下所示,其中row与column是二维阵列索引,loc表示对应的一维阵列索引:
loc = column + row*行数
以行为主的二维阵列要转为一维阵列时,是将二维阵列由左往右一行一行读入一维阵列,此时索引的对应公式如下所示:
loc = row + column*列数
公式的推导您画图看看就知道了,如果是三维阵列,则公式如下所示,其中i(个数u1)、j(个数u2)、k(个数u3)分别表示三维阵列的三个索引:
以列为主:loc = i*u2*u3 + j*u3 + k
以行为主:loc = k*u1*u2 + j*u1 + i
更高维度的可以自行依此类推,但通常更高维度的建议使用其它资料结构(例如物件包装)会比较具体,也不易搞错。
实现:
public class TwoDimArray {
public static int[] toOneDimByRow(int[][] array) {
int[] arr = new int[array.length * array[0].length];
for (int row = 0; row < array.length; row++) {
for (int column = 0; column < array[0].length; column++) {
int i = column + row * array[0].length;
arr[i] = array[row][column];
}
}
return arr;
}
public static int[] toOneDimByColumn(int[][] array) {
int[] arr = new int[array.length * array[0].length];
for (int row = 0; row < array.length; row++) {
for (int column = 0; column < array[0].length; column++) {
int i = i = row + column * array.length;
arr[i] = array[row][column];
}
}
return arr;
}
}
说明:
上三角矩阵是矩阵在对角线以下的元素均为0,即Aij = 0,i > j,例如:
1 2 3 4 5
0 6 7 8 9
0 0 10 11 12
0 0 0 13 14
0 0 0 0 15
下三角矩阵是矩阵在对角线以上的元素均为0,即Aij = 0,i < j,例如:
1 0 0 0 0
2 6 0 0 0
3 7 10 0 0
4 8 11 13 0
5 9 12 14 15
对称矩阵是矩阵元素对称于对角线,例如:
1 2 3 4 5
2 6 7 8 9
3 7 10 11 12
4 8 11 13 14
5 9 12 14 15
上三角或下三角矩阵也有大部份的元素不储存值(为0),我们可以将它们使用一维阵列来储存以节省储存空间,而对称矩阵因为对称于对角线,所以可以视为上三角或下三角矩阵来储存。
解法:
假设矩阵为nxn,为了计算方便,我们让阵列索引由1开始,上三角矩阵化为一维阵列,若以列为主,其公式为:loc = n*(i-1) - i*(i-1)/2 + j
化为以行为主,其公式为:loc = j*(j-1)/2 + i
下三角矩阵化为一维阵列,若以列为主,其公式为:loc = i*(i-1)/2 + j
若以行为主,其公式为:loc = n*(j-1) - j*(j-1)/2 + i
实现:
public class TriangleArray {
private int[] arr;
private int length;
public TriangleArray(int[][] array) {
length = array.length;
arr = new int[length * (1 + length) / 2];
int loc = 0;
for (int i = 0; i < length; i++) {
for (int j = 0; j < length; j++) {
if (array[i][j] != 0) arr[loc++] = array[i][j];
}
}
}
public int getValue(int i, int j) {
int loc = length * i - i * (i + 1) / 2 + j;
return arr[loc];
}
public static void main(String[] args) {
int[][] array = {
{
1, 2, 3, 4, 5
}, {
0, 6, 7, 8, 9
}, {
0, 0, 10, 11, 12
}, {
0, 0, 0, 13, 14
}, {
0, 0, 0, 0, 15
}
};
TriangleArray triangleArray = new TriangleArray(array);
System.out.print(triangleArray.getValue(2, 2));
}
}
说明:
将1到n(为奇数)的数字排列在nxn的方阵上,且各行、各列与各对角线的和必须相同,如下所示:
解法:
填魔术方阵的方法以奇数最为简单,第一个数字放在第一行第一列的正中央,然后向右(左)上填,如果右(左)上已有数字,则向下填,如下图所示:
一般程式语言的阵列索引多由0开始,为了计算方便,我们利用索引1到n的部份,而在计算是向右(左)上或向下时,我们可以将索引值除以n值,如果得到余数为1就向下,否则就往右(左)上,原理很简单,看看是不是已经在同一列上绕一圈就对了。
实现:
public class Matrix {
public static int[][] magicOdd(int n) {
int[][] square = new int[n + 1][n + 1];
int i = 0;
int j = (n + 1) / 2;
for (int key = 1; key <= n * n; key++) {
if ((key % n) == 1) i++;
else {
i--;
j++;
}
if (i == 0) i = n;
if (j > n) j = 1;
square[i][j] = key;
}
int[][] matrix = new int[n][n];
for (int k = 0; k < matrix.length; k++) {
for (int l = 0; l < matrix[0].length; l++) {
matrix[k][l] = square[k + 1][l + 1];
}
}
return matrix;
}
public static void main(String[] args) {
int[][] magic = Matrix.magicOdd(5);
for (int k = 0; k < magic.length; k++) {
for (int l = 0; l < magic[0].length; l++) {
System.out.print(magic[k][l] + " ");
}
System.out.println();
}
}
}
说明:
与 奇数魔术方阵 相同,在于求各行、各列与各对角线的和相等,而这次方阵的维度是4的倍数。
解法:
先来看看4X4方阵的解法:
简单的说,就是一个从左上由1依序开始填,但遇对角线不填,另一个由左上由16开始填,但只填在对角线,再将两个合起来就是解答了;如果N大于2,则以 4X4为单位画对角线:
至于对角线的位置该如何判断,有两个公式,有兴趣的可以画图印证看看,如下所示:
左上至右下:j % 4 == i % 4
右上至左下:(j % 4 + i % 4) == 1
实现:
public class Matrix2 {
public static int[][] magicFourN(int n) {
int[][] square = new int[n + 1][n + 1];
for (int j = 1; j <= n; j++) {
for (int i = 1; i <= n; i++) {
if (j % 4 == i % 4 || (j % 4 + i % 4) == 1) square[i][j] = (n + 1 - i) * n - j + 1;
else square[i][j] = (i - 1) * n + j;
}
}
int[][] matrix = new int[n][n];
for (int k = 0; k < matrix.length; k++) {
for (int l = 0; l < matrix[0].length; l++) {
matrix[k][l] = square[k + 1][l + 1];
}
}
return matrix;
}
public static void main(String[] args) {
int[][] magic = Matrix2.magicFourN(8);
for (int k = 0; k < magic.length; k++) {
for (int l = 0; l < magic[0].length; l++) {
System.out.print(magic[k][l] + " ");
}
System.out.println();
}
}
}
说明:
方阵的维度整体来看是偶数,但是其实是一个奇数乘以一个偶数,例如6X6,其中6=2X3,我们也称这种方阵与单偶数方阵。
解法:
如果您会解奇数魔术方阵,要解这种方阵也就不难理解,首先我们令n=2(2m+1),并将整个方阵看作是数个奇数方阵的组合,如下所示:
首先依序将A、B、C、D四个位置,依奇数方阵的规则填入数字,填完之后,方阵中各行的和就相同了,但列与对角线则否,此时必须在A-D与C- B之间,作一些对应的调换,规则如下:
将A中每一列(中间列除外)的头m个元素,与D中对应位置的元素调换。
将A的中央列、中央那一格向左取m格,并与D中对应位置对调
将C中每一列的倒数m-1个元素,与B中对应的元素对调
举个实例来说,如何填6X6方阵,我们首先将之分解为奇数方阵,并填入数字,如下所示:
接下来进行互换的动作,互换的元素以不同颜色标示,如下:
实现:
public class Matrix3 {
public static int[][] magic22mp1(int n) {
int[][] square = new int[n][n];
magic_o(square, n / 2);
exchange(square, n);
return square;
}
private static void magic_o(int[][] square, int n) {
int row = 0;
int column = n / 2;
for (int count = 1; count <= n * n; count++) {
square[row][column] = count;
// 填A
square[row + n][column + n] = count + n * n;
// 填B
square[row][column + n] = count + 2 * n * n;
// 填C
square[row + n][column] = count + 3 * n * n;
// 填D
if (count % n == 0) row++;
else {
row = (row == 0) ? n - 1 : row - 1;
column = (column == n - 1) ? 0 : column + 1;
}
}
}
private static void exchange(int[][] x, int n) {
int i, j;
int m = n / 4;
int m1 = m - 1;
for (i = 0; i < n / 2; i++) {
if (i != m) {
for (j = 0; j < m; j++)
// 处理规则 1
swap(x, i, j, n / 2 + i, j);
for (j = 0; j < m1; j++)
// 处理规则 2
swap(x, i, n - 1 - j, n / 2 + i, n - 1 - j);
} else {
// 处理规则 3
for (j = 1; j <= m; j++) swap(x, m, j, n / 2 + m, j);
for (j = 0; j < m1; j++) swap(x, m, n - 1 - j, n / 2 + m, n - 1 - j);
}
}
}
private static void swap(int[][] number, int i, int j, int k, int l) {
int t;
t = number[i][j];
number[i][j] = number[k][l];
number[k][l] = t;
}
public static void main(String[] args) {
int[][] magic = Matrix3.magic22mp1(6);
for (int k = 0; k < magic.length; k++) {
for (int l = 0; l < magic[0].length; l++) {
System.out.print(magic[k][l] + " ");
}
System.out.println();
}
}
}