早就说要开始系统的学算法了,可总是被这个事那个事所拖累,从今天开始,认真拿着《算法》跟着coursera上的课程学算法。
1Union_Find判断问题:
算法的设计总是由API的设计开始,先将需要解决的问题抽象化,对需要的功能写出相应的API,然后在考虑解决这个问题用什么数据结构最好,最后再开始实现各个API。
关于Union_Find问题,判断两个对象之间是否联通的一个抽象的想法便是将union的对象放进同一个集合中,查询时若两个对象在一个集合中就是有路线可以联通。相应的我们就可以用数组来模拟对象所在集合,并且我们需要union,connected这两个简单的操作,还有一个构造函数读入对象。
<1>Quick-connect算法:很明显,算法的核心问题就是如何实现union和connected这两个API。快查算法的实现非常简单,初始化时给所有对象一个id,表示每个对象处于不同的集合。进行union操作时,如果id不同,就遍历整个数组将所有和其中一个对象id相同的对象的id都改成后一个对象的id。查询操作就会非常方便,只要比较两者id是否相同,即可判断是否联通。
<2>Quick-union算法:第一种实现方法最大的问题便是每次union都要遍历数组,效率太低,Quick-union算法是模拟树的实现,union时只要将根节点相连,整个集合就合二为一了。但这种问题带来了另外一个问题,那就是可能存在“瘦高”的树,那么去查询这种树的根节点将会非常耗费时间。
<3>Quick-union improvements:不过,我们完全可以优化上一算法来解决这个问题。增加一个数组变量来记录每个树的大小,当union时,总是将小树连接在大树的根节点下。这个看似小小的操作将大大提升运算效率,在大数据量下,会将平方级的运算提升至对数级。
华丽的分割线
2.Java栈与队列的实现:
栈和队列是两种相像的经典的数据类型,都是对数据添加删除做限制的线性表。其不同之处就在于出栈(pop)和出列(dequeue)的方式。栈是先进后出,队列是先进先出。
<1>Java虽说没有所谓的指针,但其实对于每个对象的引用就是指针,所以,栈可以用链表来实现:
public class Stack {
Node first = null;
private class Node {//定义内部类,实现节点的数据类型
String value;
Node next;
}
public boolean isEmpty() {//判空函数
return first == null;
}
public void push(String item) {//入栈
Node oldfirst = first;
first = new Node();
first.value = item;
first.next = oldfirst;
}
public String pop() {//出栈
String item = first.value;
first = first.next;
return item;
}
public static void main(String[] args) {
Stack zhan = new Stack();
System.out.println(zhan.isEmpty());
zhan.push("hello");
zhan.push("world!");
System.out.println(zhan.pop());
}
}
当然,栈也可以用简单数组实现:
public class Stack {
String S[];
int n = 0;//代表了栈顶
public Stack(int capacity) {//要求客户端定义栈大小
S = new String[capacity];
}
public boolean isEmpty() {//判空函数
return n == 0;
}
public void push(String item) {//入栈
S[n++] = item;
}
public String pop() {//出栈
String item = S[--n];
S[n] = null;//这两行代码是细节,如果直接返回S[--n]数组中对对象的引用还存在,可能会造成内存浪费。
return item;
}
public static void main(String[] args) {
Stack zhan = new Stack(2);
System.out.println(zhan.isEmpty());
zhan.push("hello");
zhan.push("world!");
System.out.println(zhan.pop());
}
}
数组实现栈确实方便简单,但是它却必须规定大小,而在实际应用中我们的客户端往往不知道究竟需要多大的栈,所以我们可以对程序进行改进,动态改变数组大小:
public class Stack {
String S[] = new String[1];
int n = 0;
public boolean isEmpty() //判空函数
{
return n == 0;
}
public void push(String item) //入栈
{
S[n++] = item;
if( n == S.length )
resize(2*S.length);
}
public String pop() //出栈
{
String item = S[--n];
S[n] = null;
if( n == S.length/4)//如果判断是否等于S.length/2可能会出现“抖动”现象
resize(S.length/2);
return item;
}
private void resize(int capacity)//重规定数组大小
{
String copy[] = new String[capacity];
for( int i = 0; i<n; i++ )
copy[i] = S[i];
S = copy;
}
public static void main(String[] args) {
Stack zhan = new Stack();
System.out.println(zhan.isEmpty());
zhan.push("hello");
zhan.push("world!");
System.out.println(zhan.pop());
}
}
这样就解决了数组实现栈大小限定的弊端。
对于两种不同的实现,我们应当发现,数组的基本操作要比链表更快,这就使得在处理数据时整体上是数组实现的栈效率更高。不过当数组进行动态伸缩的时候,操作时间会明显变长。所以可以根据实际需求选择实现方法。
<2>对于队列的实现,基本上和栈类似,但是链表实现我们要维护两个节点:first和last,enqueue时从last节点入队,dequeue时从first节点出队:
public class Queue {
private Node first, last;
private class Node{
String value;
Node next;
}
public boolean isEmpty()//判空函数
{
return first == null;
}
public void enqueue(String value)//从last节点入队
{
Node oldlast = last;
last = new Node();
last.value = value;
last.next = null;
if(isEmpty())
first = last;
else
oldlast.next = last;
}
public String dequeue()//从first节点出队
{
String item = first.value;
first = first.next;
return item;
}
public static void main(String[] args) {
Queue duilie = new Queue();
System.out.println(duilie.isEmpty());
duilie.enqueue("hello");
duilie.enqueue("world!");
System.out.println(duilie.dequeue());
}
}
以下是数组实现:
public class Queue {
private int first, last;
String S[] = new String[1];
public boolean isEmpty()//判空函数
{
return first == last;
}
public void enqueue(String value)//入队
{
S[last++] = value;
if( last == S.length )
resize(2*S.length);
}
public String dequeue()//出队
{
String item = S[first];
S[first++] = null;
if( last == S.length/4 )
resize(S.length/2);
return item;
}
private void resize(int size)//此处重定义数组比栈稍微复杂,需要找到first节点,将其重新归零
{
String copy[] = new String[size];
int j = 0;
while( S[j] == null)
j++;
for( int i = 0; i<last; i++ )
copy[i] = S[i+j];
S = copy;
}
public static void main(String[] args) {
Queue duilie = new Queue();
System.out.println(duilie.isEmpty());
duilie.enqueue("hello");
duilie.enqueue("world!");
System.out.println(duilie.dequeue());
}
}
数组实现多了一些栈不需要考虑的因素,关于两种实现方法的性能分析同栈。
(关于背包的实现,就是一个不可以取出的栈,除掉pop方法即可。)
华丽的分割线
3.初级排序算法
<1>选择排序:
public class Selection_sort {
public static void sort(int[] a)
{
for( int i = 0; i<a.length; i++ )
{
for( int j = i; j>=1; j-- )
{
if( a[j]<a[j-1])
{
int t =a[j];
a[j] = a[j-1];
a[j-1] = t;
}
}
}
}
public static void main(String[] args) {
int a[] = {23,324,45,32,2,3,123,5,234,52,3,67,95};
sort(a);
for( int i = 0; i<a.length; i++ )
{
System.out.println(a[i]);
}
}
}
<2>插入排序:
public class Insertion_sort {
public static void sort(int[] a)
{
for( int i = 0; i<a.length; i++ )
{
for( int j = i; j<a.length; j++ )
{
if( a[j]<a[i])
{
int t =a[j];
a[j] = a[j-1];
a[j-1] = t;
}
}
}
}
public static void main(String[] args) {
int a[] = {23,324,45,32,2,3,123,5,234,52,3,67,95};
sort(a);
for( int i = 0; i<a.length; i++ )
{
System.out.println(a[i]);
}
}
}
<3>希尔排序:
public class Shellsort {
public static void sort(int[] a)
{
int h = 1;
while( h<a.length/3 )
h = 3*h+1;
while( h>=1)
{
for( int i = h; i<a.length; i++ )
{
for( int j = i; j>=h; j-=h )
{
if( a[j]<a[j-h] )
{
int t = a[j];
a[j] = a[j-h];
a[j-h] = t;
}
}
}
h = h/3;
}
}
public static void main(String[] args) {
int a[] = {23,324,45,32,2,3,123,5,234,52,3,67,95};
sort(a);
for( int i = 0; i<a.length; i++ )
{
System.out.println(a[i]);
}
}
}
华丽的分割线
4.归并排序
public class Mergesort {
public static void sort(int[] a)
{
int item[] = new int[a.length];
sort(a, item, 0, a.length-1);
}
private static void sort(int[] a, int[] item, int head, int tail)
{
if( head>=tail )
return;
int middle = (head+tail)/2;
sort(a, item, head, middle);
sort(a, item, middle+1, tail);
merge(a, item, head, tail, middle);
}
private static void merge(int[] a, int[] item, int head, int tail, int middle)
{
for( int i = head; i<=tail; i++ )
item[i] = a[i];
int j = head;
int k = middle+1;
for( int i = head; i<=tail; i++ )
{
if( j>middle )
a[i] = item[k++];
else if( k>tail )
a[i] = item[j++];
else if( item[j]<=item[k] )
a[i] = item[j++];
else
a[i] = item[k++];
}
}
public static void main(String[] args) {
int a[] = {23,324,45,32,2,3,123,5,234,52,3,67,95};
sort(a);
for( int i = 0; i<a.length; i++ )
{
System.out.println(a[i]);
}
}
}
自底向上的归并排序:
public class Mergesort {
private static int item[];
public static void sort(int[] a)
{
item = new int[a.length];
sort(a, 0, a.length-1);
}
private static void sort(int[] a, int head, int tail)
{
for( int i = 1; i<a.length; i=i*2 )
{
for( int j = head; j<a.length; j=j+i+i )
{
merge(a, j, Math.min(a.length-1, j+i+i-1), j+i-1);
}
}
}
private static void merge(int[] a, int head, int tail, int middle)
{
for( int i = head; i<=tail; i++ )
item[i] = a[i];
int j = head;
int k = middle+1;
for( int i = head; i<=tail; i++ )
{
if( j>middle )
a[i] = item[k++];
else if( k>tail )
a[i] = item[j++];
else if( item[j]<=item[k] )
a[i] = item[j++];
else
a[i] = item[k++];
}
}
public static void main(String[] args) {
int a[] = {23,324,45,32,2,3,123,5,234,52,3,67,95};
sort(a);
for( int i = 0; i<a.length; i++ )
{
System.out.println(a[i]);
}
}
}