java一些必会算法(转自落尘曦的博客:http://blog.csdn.net/qq_23994787。 )
經典算法的Java實現
(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());
}
}
?
?
?
?
?
?
經典算法的Java實現
(1)河內塔問題:
說明:
河內之塔(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);
}
}
}
?
(2)費式數列
說明:
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();
}
}
?
(3)巴斯卡(Pascal)三角形
說明:
巴斯卡(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();
}
}
?
?
(4)蒙地卡羅法求 PI
說明:
蒙地卡羅為摩洛哥王國之首都,該國位于法國與義大利國境,以賭博聞名。蒙地卡羅的基本原理為以亂數配合面積公式來進行解題,這種以機率來解題的方式帶有賭博的意味,雖然在精確度上有所疑慮,但其解題的思考方向卻是個值得學習的方式。
解法:
蒙地卡羅的解法適用于與面積有關的題目,例如求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);
}
}
?
(5)最大公因數、最小公倍數
說明:
解法:
最大公因數使用輾轉相除法來求,最小公倍數則由這個公式來求:
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));
}
}
?
(6)阿姆斯壯數
說明:
在三位的整數中,例如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();
}
}
?
(7)最大訪客數
說明:
現將舉行一個餐會,讓訪客事先填寫到達時間與離開時間,為了掌握座位的數目,必須先估計不同時間的最大訪客數。
解法:
這個題目看似有些復雜,其實相當簡單,單就計算訪客數這個目的,同時考慮同一訪客的來訪時間與離開時間,反而會使程式變得復雜;只要將來訪時間與離開時間分開處理就可以了,假設訪客?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));
}
}
}
?
(8)洗撲克牌(亂數排列)
說明:
洗撲克牌的原理其實與亂數排列是相同的,都是將一組數字(例如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("");
}
}
}
?
(9)約瑟夫問題(Josephus Problem)
說明:
據說著名猶太歷史學家?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();
}
}
?
(10)排列組合
說明:
將一組數字、字母或符號進行排列,以得到不同的組合順序,例如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);
}
}
?
(11)得分排行
說明:
假設有一教師依學生座號輸入考試分數,現希望在輸入完畢后自動顯示學生分數的排行,當然學生的分數可能相同。
?
解法:
這個問題基本上要解不難,只要使用額外的一個排行陣列走訪分數陣列就可以了,直接使用下面的程式片段作說明:
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]);
}
}
}
?
(12)選擇、插入、氣泡排序
說明:
選擇排序(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 + " ");
}
}
?
(13)快速排序(一)
說明:
快速排序法(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;
}
}
?
?
(14)快速排序(二)
說明:
在快速排序法(一)中,每次將最左邊的元素設為軸,而之前曾經說過,快速排序法的加速在于軸的選擇,在這個例子中,只將軸設定為中間的元素,依這個元素作基準進行比較,這可以增加快速排序法的效率。
解法:
在這個例子中,取中間的元素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;
}
}
?
?
?
(15)快速排序(三)
說明:
之前說過軸的選擇是快速排序法的效率關鍵之一,在這邊的快速排序法的軸選擇方式更加快了快速排序法的效率,它是來自演算法名書?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;
}
}
?
(16)合并排序
說明:
之前所介紹的排序法都是在同一個陣列中的排序,考慮今日有兩筆或兩筆以上的資料,它可能是不同陣列中的資料,或是不同檔案中的資料,如何為它們進行排序?
?
解法:
可以使用合并排序法,合并排序法基本是將兩筆已排序的資料合并并進行排序,如果所讀入的資料尚未排序,可以先利用其它的排序方式來處理這兩筆資料,然后再將排序好的這兩筆資料合并。
有人問道,如果兩筆資料本身就無排序順序,何不將所有的資料讀入,再一次進行排序?排序的精神是盡量利用資料已排序的部份,來加快排序的效率,小筆資料的排序較為快速,如果小筆資料排序完成之后,再合并處理時,因為兩筆資料都有排序了,所有在合并排序時會比單純讀入所有的資料再一次排序來的有效率。
那么可不可以直接使用合并排序法本身來處理整個排序的動作?而不動用到其它的排序方式?答案是肯定的,只要將所有的數字不斷的分為兩個等分,直到最后剩一個數字為止,然后再反過來不斷的合并,就如下圖所示:
?
不過基本上分割又會花去額外的時間,不如使用其它較好的排序法來排序小筆資料,再使用合并排序來的有效率。
?
實現:
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;
}
}
?
(17)基數排序
說明:
在之前所介紹過的排序方法,都是屬于「比較性」的排序法,也就是每次排序時?,都是比較整個鍵值的大小以進行排序。
這邊所要介紹的「基數排序法」(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] + " ");
}
}
}
?
?
(18)循序查找法(使用衛兵)
說明:
搜尋的目的,是在「已排序的資料」中尋找指定的資料,而當中循序搜尋是最基本的搜尋法,只要從資料開頭尋找到最后,看看是否找到資料即可。
?
解法:
????初學者看到循序搜尋,多數都會使用以下的方式來進行搜尋:
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("找不到數值");
}
}
?
(19)二分查找法
說明:
如果搜尋的數列已經有排序,應該盡量利用它們已排序的特性,以減少搜尋比對的次數,這是搜尋的基本原則,二分搜尋法是這個基本原則的代表。
解法:
????在二分搜尋法中,從數列的中間開始搜尋,如果這個數小于我們所搜尋的數,由于數列已排序,則該數左邊的數一定都小于要搜尋的對象,所以無需浪費時間在左邊的數;如果搜尋的數大于所搜尋的對象,則右邊的數無需再搜尋,直接搜尋左邊的數。
所以在二分搜尋法中,將數列不斷的分為兩個部份,每次從分割的部份中取中間數比對,例如要搜尋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("找不到數值");
}
}
?
(20)插補查找法
說明:
如果卻搜尋的資料分布平均的話,可以使用插補(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("找不到數值");
}
}
?
?
(21)費式查找法
說明:
二分搜尋法每次搜尋時,都會將搜尋區間分為一半,所以其搜尋時間為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("找不到數值");
}
}
?
?
?
(22)稀疏矩陣
說明:
????如果在矩陣中,多數的元素并沒有資料,稱此矩陣為稀疏矩陣(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();
}
}
}
?
?
?
(23)多維矩陣轉一維矩陣
說明:
????有的時候,為了運算方便或資料儲存的空間問題,使用一維陣列會比二維或多維陣列來得方便,例如上三角矩陣、下三角矩陣或對角矩陣,使用一維陣列會比使用二維陣列來得節省空間。
解法:
以二維陣列轉一維陣列為例,索引值由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;
}
}
?
?
(24)上三角、下三角、對稱矩陣
說明:
????上三角矩陣是矩陣在對角線以下的元素均為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));
}
}
?
?
(25)奇數魔方陣
說明:
????將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();
}
}
}
?
?
(26)4N魔方陣
說明:
????與?奇數魔術方陣?相同,在于求各行、各列與各對角線的和相等,而這次方陣的維度是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();
}
}
}
?
?
?
(27)2(2n+1)魔方陣
說明:
????方陣的維度整體來看是偶數,但是其實是一個奇數乘以一個偶數,例如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();
}
}
總結
以上是生活随笔為你收集整理的java一些必会算法(转自落尘曦的博客:http://blog.csdn.net/qq_23994787。 )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java实现印刷体转手写体—妈妈再也不用
- 下一篇: 四则运算及感想