// insert element at the current position voidinsert(const E& it){ Assert(listSize>=maxSize, “Exceed capacity”); //shift Elements up for(int i=listSize; i>curr; i--) listArray[i] = listArray[i-1]; listArray[curr] = it; // insert the element listSize++; // increment list size }
remove
1 2 3 4 5 6 7 8 9 10 11
// Remove and return the current element E remove(){ Assert((curr>=0)&&(curr<listSize), "no element"); E it = listArray[curr]; // Copy the element for(int i=curr; i<listSize-1; i++) // Shift them down listArray[i] = listArray[i+1]; listSize--; // Decrement size return it; }
// An implementation of a simple // singly-linked list node
template <typename E> classLink { public: E element; //value for this node Link *next; //Pointer to next node in list //Constructors Link(const E& elemval, Link* nextval =NULL) { element = elemval; next = nextval; } Link(Link* nextval =NULL) { next = nextval; } };
//Class LList //inherit the abstract class List template <typename E> classLList: public List<E>{ private: Link<E>* head; //Pointer to list header Link<E>* tail; //Pointer to last element Link<E>* curr; //access to current element int cnt; //Size of list voidinit()//used by constructor { curr = tail = head = new Link<E>; cnt =0;} voidremoveall()//used by deconstructor { while(head != NULL) { curr=head; head=head->next; delete curr; } } }
insert
1 2 3 4 5 6 7 8 9 10 11 12 13
//Insert a node to current position public: voidinsert(const E& it){ curr->next = newLink<E>(it, curr->next); if (tail == curr) tail = curr->next; //new tail cnt++; } //Append a node at the tail of the list voidappend(const E& it){ tail = tail->next = newLink<E>(it, NULL); cnt++; }
// Remove and return current element 删除curr指针后的节点 E remove(){ E it = curr->next->element; Link<E> *ltemp = curr->next; if (tail == curr->next) tail =curr; // Reset tail curr->next = curr->next->next; //remove element delete ltemp; //reclaim space cnt--; //decrement the list size return it; } //Next – move curr one pos toward the tail voidnext(){ // no change if already at end if (curr != tail) { curr = curr->next;} } //Prev – move curr one pos toward the head voidprev(){ if (curr == head) return; // No previous element Link<E>* temp = head; //march down list until the previous element while (temp->next!=curr) temp=temp->next; curr = temp; }
//Array-based Implementation (solution 2) template <typename E> classAQueue: public Queue<E> { private: int maxSize; // Maximum size of queue int front; // Index of front element int rear; // Index of rear element E *listArray; //Array holding queue elements public: AQueue(int size =defaultSize) { // Constructor- Make list array one position // larger for empty slot maxSize = size+1; rear = 0; front = 1; listArray = new E[maxSize]; } ˜ AQueue() { delete [] listArray; } //reinitializeS voidclear(){rear = 0; front = 1;} voidenqueue(const E& it){//put “it” in queue Assert(((rear+2) % maxSize) != front, “Queue is full”); rear = (rear+1)%maxSize; listArray[rear] = it; } E dequeue(){//take element out Assert(length() != 0, “Queue is empty”); E it = listArray[front]; front =(front+1)%maxSize; return it; } //get front value const E& frontValue()const{ Assert(length()!=0, “Queue is empty”); return listArray[front]; }
voidbuildHeap(){ for( int i = currentSize / 2; i > 0; --i)//从最右边的叶子节点的父亲开始向前的所有节点都执行下滤操作 percolateDown(i); }
$时间复杂度为 O(N)$
第 7 章 排序
稳定性
选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法。
冒泡排序、插入排序、归并排序、基数排序是稳定的排序算法。
选择排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/* * Selection Sort * In the ith pass, the ith smallest key in the array is selected * and placed into position i */ template <typename E, typename Comp> voidselsort(E A[], int n){ for (int i=0; i<n-1; i++) { //select ith record int lowindex = i; //Remember its index for (int j=n-1; j>i; j--) //Find least value //找到从第 i+1 个数字到尾部数字中的最小值 // 每次都将第 i 个最小的元素放在其应当放在的位置 // 假如有两个相同大的数放在不同位置很显然其相对次序会被打乱,所以选择排序并不是稳定的 if (Comp::lt(A[j], A[lowindex])) lowindex = j; // Put it in place swap(A, i, lowindex); } }
/* * Insertion sort * Each record is inserted in turn at the correct position * within a sorted list composed of those records already * processed */ template <typename E, typename Comp> voidinsertionSort(E A[], int n){ for (int i=1; i<n; i++) //insert i’th record for (int j=i; (j>0) && (Comp::prior(A[j], A[j-1])); j--) // A[ j ] < A[ j-1 ],从 i 开始进行比较 swap(A, j, j-1);// A[j] = A[j-1] }
template <typename Comparable> voidshellsort( vector<Comparable> & a ){ for( int gap = a.size() / 2; gap > 0; gap /= 2 ) for( int i=gap; i < a.size(); ++i ){ Comparable tmp = std::move( a[ i ] ); int j=i; for( ; j>=gap && tmp < a[ j-gap ]; j -= gap ) a[ j ] = std::move( a[ j- gap ] ); a[ j ] = std::move ( tmp ); } }
希尔排序的修改版 就是对于差一个 gap 的位置上的一系列元素进行插入排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// Modified version of Insertion Sort template <typename E, typename Comp> voidinssort2(E A[], int n, int incr){ for (int i=incr; i<n; i+=incr) for (int j=i; (j>=incr) && (Comp::prior(A[j], A[j-incr])); j-=incr) swap(A, j, j-incr); } //Shellsort template <typename E, typename Comp> voidshellsort(E A[], int n){ for (int i=n/2; i>2; i/=2) //For each increment for (int j=0; j<i; j++) //Sort each sublist inssort2<E,Comp>(&A[j], n-j, i); //Normal insertion sort inssort2<E,Comp>(A, n, 1); }
// 返回 left,center,right 三项的中值,将他们排序并隐匿枢纽元 template <typename Comparable> const Comparable & median3( vector<Comparable> & a,int left,int right ) { if( a[ center ] ‹ a[ left ]) std:: swap ( a[ left ], a[ center ] ); if a[right] < al left ] ) std:: swap ( a[ left ], a[ right] ); if a[right] < a[ center ] ) std:: swap ( a[ center ], a[ right ] ); // 将枢纽元置于 right-1处 std:: swap ( a[ center ], al right - 1] ); return a[ right - 1 ]; }
/** * 进行递归调用的内部快速排序方法, * 使用三数中值分割法,以及截止范围是10的截止技术. * a 是Comparable 项的数组. * left 为子数组最左元素的下标。 * right 为子数组最右元素的下标. * template <typename Comparable> void quicksort( vector<Comparable> & a,int left,int right) { if( left + 10 <= right ) { const Comparable & pivot = midian3( a, left, right ) //开始分割 int i = left, j = right -1; for( ; ; ) { //因为 left,right,center 三者已经排序,所以 left 处的元素一定小于 等于pivot,right 处元素一定大于等于 pivot,而 right-1 存放的是 pivot,所以 j 从 right-1 处开始,++i,--j 直接从 1 和 right-2 开始进行比较 while( a[ ++i ] < pivot ){} while( pivot < a[ --j ] ){} if( i < j ) std::swap( a[ i ],a[ j ] ); else break; } //i 最后指向第一个大于等于 pivot 的位置,因为 pivot 在 right-1 所以必须要找到偏大一点的分界点而不是 j 所代表的偏小的分界点 std::swap( a[ i ],a[ right - 1 ] );//恢复枢纽元 quicksort( a, left, i-1 ); quicksort( a, i + 1, right ); } else insertionSort( a, left, right ); } // 另一种实现方式 template <typename E, typename Comp> int partition(E A[], int l, int r, E& pivot) { do { //Move the bounds inward until they meet //Move l right and r left while (Comp::prior(A[++l], pivot)); while ((l<r) && Comp::prior(A[--r],pivot)); // Swap out-of-place values swap(A, l, r); } while (l < r); // Stop when they cross swap(A, l, r); // Reverse last swap[最后一次交换是多余的] return l; //Return 1st position in right part }
如果|S|=1,那么 k=1 并且将 S 中的元素作为答案返回,如果正在使用小数组的截止方法且|S|$ \leq CUTOFF$,则将 S 排序并且返回第 k 个元素
选取一个枢纽元$v \in S$
将集合$S-{v}$ 分割成 $S_{1}和 S_{2}$,过程就像在快速排序中所做的一样
如果 $k \leq | S_{1} |$,那么第 k 个最小元必然在$S_{1}$中,所以返回 quickselect($S_{1}$,k)。如果 k=1+ | $S_{1}$ |,那么枢纽元就是第 k 个最小元,将其作为答案返回。否则这第 k 个最小元就在 $S_{2}$中,它是$S_{2}$中的第(k- | $S_{1}$ |-1 )的最小元,返回 quickselect($S_{2}$,k- | $S_{1}$ | - 1)。 但是快速选择的平均运行时间为 $O(N)$
7.8 桶排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/** * Bucket Sort * Allow for duplicate values among keys * Allow for a set of N records falling in a range larger * than N ([0,MaxKeyValue-1]) **/ template <typename E, classgetKey> voidbucketsort(E A[], int n){ List<E> B[MaxKeyValue]; //An array of linked lists E item; //assign records to bins for (i=0; i<n; i++) //All records with key value i are placed in bin B[i] B[getKey::key(A[i])].append(getKey::key(A[i])); //process MaxKeyValue bins to output records for (i=0; i<MaxKeyValue; i++) for (B[i].setStart(); B[i].getValue(item); B[i].next()) output(item); }
/* * Radix sort an array of Strings. * Assume all characters are ASCII, residing in the first 256 * positions of the Unicode character set. * Assume all have same length(stringLen). */ voidradixSortA( vector<string> & arr, int stringLen ){ constint BUCKETS = 256; vector<vector<string>> buckets( BUCKETS ); for( int pos = stringLen - 1; pos >= 0; --pos ){ for( string & s : arr ) //Adds s at the end of the buckets[ s[ pos ] ] buckets[ s[ pos ] ].push_back( std::move( s ) ); int idx = 0; for( auto & thisBucket : buckets ){ for( string & s : thisBucket ) arr[ idx++ ] = std::move( s ); thisBucket.clear( ); } } }
/* * Counting radix sort * B[] is array for buckets * cnt[i] stores numbers of records in bucket[i] * b is numbers of buckets(base), r is number of passes */ template <typename E, typename getKey> voidradix(E A[], E B[], int n, int r, int b, int cnt[]){ int j; for (int i=0, btoi=1; i<r; i++, btoi*=b) {//for r digits for (j=0; j<b; j++) cnt[j] = 0; //Count # of records for each buckets on this pass for(j=0; j<n; j++) cnt[(getKey::key(A[j])/btoi)%b]++; //Index B: cnt[j] will be index for last slot of bucket j. for (j=1; j<b; j++) cnt[j] += cnt[j-1] ; /*Put records into buckets, from bottom of each bucket.*/ for (j=n-1; j>=0; j--) B[--cnt[(getKey::key(A[j])/btoi)%b]] = A[j]; for (j=0; j<n; j++) A[j] = B[j]; //Copy B back to A. } }
找到第 k 小的数字需要 N-k+$\lceil log \binom{N}{k-1} \rceil$
找到第 k 小元素的决策树至少有 $\binom{N}{k-1}2^{N-k}$个树叶
找到最小元素至少需要 N-1次比较
7.11 外部排序 External Sorting
用于处理很大的输入,因为输入太大为无法被放入内存中
记录被储存在磁带中,只能被顺序的进行访问
假设算法有至少三条磁带用于执行
假设内存最多同时可以存储并且排序 M 条记录 算法一共需要$\lceil log(N/M) \rceil$轮 如果使用 k-way merge 也就是在 k 条记录中选取最小值那么这个数字可以变为$\lceil log_{k}(N/M) \rceil$
Replacement selection
replacement selection 可以平均让每一轮的长度为 2M 这是一种堆排序的变式
读取 M 条数据到内存中,设置 last=m-1(指向堆数组中最后一个元素)
将 M 个数据放入最小堆
重复以下步骤直到堆为空
deleteMin 并且将最小值写到输出磁带上
让 R 为下一个输入磁带中读取的记录,如果 R 大于刚才的输出就将 R 放在 root 处,否则就用 LAST 处的数据放在 root 处并且将 R 放在 LAST 位置,将 LAST 设置为 LAST-1(堆的大小减少 1)
重新排序堆(保持堆序)
第 8 章 不相交集类(并查集)
并查集类是一个在线的问题,因为 find 解决的的合并问题是在合并操作之间进行的
构造函数
1
DisjSets::DisjSets( int numElements ) : s{ numElements, -1 }
任意合并:使第二棵树成为第一棵树的子树
普通的任意合并对于一系列操作的时间复杂度可能达到$\Omega(MN)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
voidDisjSets::unionSets( int root1, int root2 ) { s[ root2 ] = root1; }
/** * Perform a find. * Error checks omitted again for simplicity. * Return the set containing x. */ intDisjSets::find( int x )const { if( s[ x ] < 0 ) return x; else returnfind( s[ x ] ); }
union by size 将大小小的树合并到大小较大的树上
如果所有 union 都以按照大小合并进行,那么可以保证任何节点的深度都不会超过 $O(logN)$ 对于按大小合并的算法,find 的运行时间是 $O(logN)$ 由于实际上只使用一个数组,所以可以让每个座位数组元素的根包含它的树大小的负值,这样初始时树的数组表示就都是-1。 当执行一次 union 的时候,要检查树的大小并更新为两棵旧的树的和,如果使用按大小求并则 连续 M 次 union 运算需要$O(M)$平均时间
union by height/rank 使得高度较小的树成为高度较高的树的子树
同样保证所有的树的深度最多就是$O(logN)$ 这是一种平缓的算法,因为只有两棵相等深度的树求并的时候树的高度才会增加 由于开始的时候高度是 0 不是负数,所以我们实际上存储高度的负值并且减去一个附加的 1 union by height/rank
void Graph::unweighted( Vertex s ) { Queue<VErtex> q; for each Vertex v v.dist = INFINITY; s.dist = 0;//源点 s.enqueue( s );
while( !q.isEmpty( ) ) { Vertex v = q.dequeue( ); for each Vertex w adjacent to v if( w.dist == INFINITY ) { w.dist = v.dist + 1; w.path = v; q.enqueue( w ); } } }
Dijkstra 算法
在每个阶段,Dijkstra 算法选择一个在所有 unknown 顶点中具有最小的$d_{v}$的顶点,同时声名从 s 到 v 的最短路径是 known 的,阶段的其余部分由 $d_{w}$的更新工作组成。 和 bfs 是一样的,如果所有的边都是正权边那么第一次遇到顶点并处理的时候一定得到最小的距离,所以在第二次遇到的时候如果节点已经声明为 known 那么就不做任何修改。 Dijkstra
void Grapgh::dijkstra( Vertex s ) { for each Vertex v { v.dist = INFINITY; v.known = false; } s.dist = 0; while( ther is an unkonwn distance vertex ) { Vertex v = samllest unknown distance vertex; v.known = true; for each Vertex w adjacent to v if( !w.known ) { DistType cvw = cost of from v to w; if( v.dist + cvw < w.dist ) { decrease( w.dist to v.dist + cvw ); w.path = v; } } } }
生成树
一个图的生成树点集和原来相同,边集是原来边集的子集且是联通的,并且在生成树的边集中没有环
DFS的路径就是一颗生成树
DFS算法也可以用来找联通块
每个联通块都有相同的标号,不同的联通块之间的标号不相同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
Main { i : integer for i = 1 to n do M[i] := 0; //initial label is zero label := 1; for i = 1 to n do if M[i] = 0 then DFS(G,M,i,label); //if i is not labeled label := label + 1; //then call DFS } DFS(G[]: node ptr array, M[]: int array, i,label: int) { v : node pointer; M[i] := label; v := G[i]; // first neighbor // while v != null do// recursive call (below) if M[v.index] = 0 then DFS(G,M,v.index,label); v := v.next; // next neighbor // }
时间复杂度和空间复杂度都是$O(n+m)$
dfs
1 2 3 4 5 6 7 8 9
voidDFS(Graph* G, int v){ PreVisit(G, v); // Take some actions G->setMark(v, VISITED); //mark v for (int w=G->first(v); w<G->n(); w=G->next(v,w)) if (G->getMark(w) == UNVISITED) DFS(G, w); PostVisit(G, v); // Take some actions }
bfs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
voidBFS(Graph* G, int start, Queue<int>*Q){ int v, w; Q->enqueue(start); // Initialize Q G->setMark(start, VISITED);
while (Q->length() != 0) { // Process Q v = Q->dequeue(); PreVisit(G, v); // Take some actions
//Queue-based topological sort voidtopsort(Graph* G, Queue<int>* Q){ int InDgree[G->n()]; //the size is the number of vertices int v, w; for (v=0; v<G->n(); v++) InDgree[v] = 0; //initialize //compute indgree for every vertex for (v=0; v<G->n(); v++) // Process every edge for (w=G->first(v); w<G->n(); w=G->next(v,w)) InDgree [w]++; // Add to w's InDgree
for (v=0; v<G->n(); v++) // Initialize Queue Q if (InDgree[v] == 0) // enqueue v with no prerequisites Q->enqueue(v); while (Q->length() != 0) { //process vertices in Q v = Q->dequeue(); printout(v); // output v for (w=G->first(v); w<G->n(); w=G->next(v,w)) { InDgree[w]--; // One less prerequisite if (InDgree[w] == 0) //vertex v is now free Q->enqueue(w); } } }
dfs实现
当一个节点已经被访问过的时候,什么也不做
当一个递归pop back回原来节点的时候将该节点输出
这个算法将以逆序输出拓扑排序的结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
voidtopsort(Graph* G){ int i; for (i=0; i<G->n(); i++) // Initialize Mark G->setMark(i, UNVISITED); for (i=0; i<G->n(); i++) // Process vertices if (G->getMark(i) == UNVISITED) tophelp(G, i); // Call helper }
voidtophelp(Graph* G, int v){ // Process v G->setMark(v, VISITED); for (int w=G->first(v); w<G->n(); w=G->next(v,w)) if (G->getMark(w) == UNVISITED) tophelp(G, w); printout(v); // output v }
Shortest-Path Algorithms
无权最短路使用BFS就可以解决
1 2 3 4 5 6 7 8 9 10 11 12 13
Distance[s] := 0 Enqueue(Q,s); Mark(s); //After a vertex is marked once // it won’t be enqueued again while queue is not empty do X := Dequeue(Q); for each vertex Y adjacent to X do if Y is unmarked then Distance[Y] := Distance[X] + 1; Previous[Y] := X; //if we want to record paths Enqueue(Q,Y); Mark(Y);
//Implementation of Dijkstra’s algorithm //compute shortest path dists from “s” voidDijkstra(Graph* G, int* D, int s){ int i, v, w; for (int i=0; i<G->n(); i++) // Initialize D[i] = INFINITY; D[s] = 0; for (i=0; i<G->n(); i++) { //process vertices //find the unvisited vertex with min dist v = minVertex(G, D); if (D[v] == INFINITY) return; //v is unreachable
G->setMark(v, VISITED); //update the distance of v’s neighbors for (w=G->first(v); w<G->n(); w = G->next(v,w)) if (G->getMark(w) == UNVISITED) if (D[w] > (D[v] + G->weight(v, w))) D[w] = D[v] + G->weight(v, w); } }
问题在于如何实现minVertex函数,找到距离最小的未访问节点
扫描一遍所有|V|个节点找到最小值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
intminVertex(Graph* G, int* D){ int i, v = -1; //initialize v to some unvisited vertex for (i=0; i<G->n(); i++) if (G->getMark(i) == UNVISITED) { v = i; break; } // Now find smallest D value for (i++; i<G->n(); i++) if ((G->getMark(i) == UNVISITED) && (D[i] < D[v])) v = i; return v; }
// Implementation using the priority queue // Class for elements in the heap Class DijkElem { Public: int vertex, distance; DijkElem() {vertex = -1; distance = -1;} DijkElem(int v, int d) {vertex = v; distance = d}; }; //Implementation of Dijkstra’s algorithm voidDijkstra(Graph* G, int* D, int s){ int i, v, w; // v is current vertex DijkElem temp; DijkElem E[G->e()]; // Heap array
// get an unvisited vertex with smallest distance for (i=0; i<G->n(); i++) { do { if(H.size() == 0) return; // Nothing to remove temp = H.removefirst(); //delmin v = temp.vertex; } while (G->getMark(v) == VISITED);
G->setMark(v, VISITED); //mark the vertex
if (D[v] == INFINITY) return; //unreachable for(w=G->first(v); w<G->n(); w=G->next(v,w)) if (G->getMark(w) == UNVISITED) if (D[w] > (D[v] + G->weight(v, w))) { //update D D[w] = D[v] + G->weight(v, w); temp.distance = D[w]; temp.vertex = w; // Insert new distance in heap H.insert(temp); } } }
时间复杂度为$\Omega((|V|+|E|)log(|E|))$
多源最短路稀疏图可以跑|V|次Dijkstra,稠密图直接用Floyd就可以
具有负边值的图
在每一个阶段让一个顶点 v 出队,找出所有邻接到 v 使得$d_{w}>d_{v}+C_{v,w}$的顶点 w,并且更新$d_{w}和 p_{w}$,并在 w 不在队列中的时候将 w 放在队列中 Bellman-Ford
void Graph::wightedNegative ( Vertex s ) { Queue<Vertex> q; for each Vertex v v.dist = INFINITY; s.dist = 0; q.enqueue( s ); while( !q.isEmpty( ) ) { Vertex v = q.dequeue( ); for each Vertex w adjacent to v if( v.dist + cvw < w.dist ) { w.dist = v.dist + cvw; w.path = v; if (w is not already in q ) q.enqueue( w ); } } }
#include<iostream> #include<list> usingnamespace std; intmain() { int i,j, n, m, mPrime, numLeft; list <int > L; list<int>::iterator iter; //Initialization cout<<"enter N (# of people) & M (# of passes before elimination):"; cin>>n>>m; numLeft = n; mPrime = m % n; for (i =1 ; i <= n; i++) L.push_back(i); iter = L.begin(); // Pass the potato for (i = 0; i < n; i++) { mPrime = mPrime % numLeft; if (mPrime <= numLeft/2) // pass forward for (j = 0; j < mPrime; j++) { iter++; if (iter == L.end()) iter = L.begin(); } else// pass backward for (j = 0; j < mPrime; j++) { if (iter == L.begin()) iter = --L.end(); else iter--; } cout<<*iter<<" "; iter= L.erase(iter); if (iter == L.end()) iter = L.begin(); } cout<<endl; return0; }
This requires a doubly linked list with pointers to the head and the tail In fact it can be implemented with a list by just renaming the list operations.