資料結構與演算法-向量
阿新 • • 發佈:2020-07-26
向量
介面與實現
ADT介面
資料結構 = 基於某種特定語言,實現ADT的一整套演算法
作為一種抽象資料型別,向量物件支援以下介面:
操作例項
模板類
* * 向量模板 *0009 typedef int Rank; //秩 * 0010 #define DEFAULT_CAPACITY 3 //預設的初始容量(實際應用中可設定為更大) * 0011 * 0012 template <typename T> class Vector { //向量模板類 * 0013 protected: * 0014 Rank _size; int _capacity; T* _elem; //規模、容量、資料區 * 0015 void copyFrom ( T const* A, Rank lo, Rank hi ); //複製陣列區間A[lo, hi) * 0016 void expand(); //空間不足時擴容 * 0017 void shrink(); //裝填因子過小時壓縮 * 0018 bool bubble ( Rank lo, Rank hi ); //掃描交換 * 0019 void bubbleSort ( Rank lo, Rank hi ); //起泡排序演算法 * 0020 Rank max ( Rank lo, Rank hi ); //選取最大元素 * 0021 void selectionSort ( Rank lo, Rank hi ); //選擇排序演算法 * 0022 void merge ( Rank lo, Rank mi, Rank hi ); //歸併演算法 * 0023 void mergeSort ( Rank lo, Rank hi ); //歸併排序演算法 * 0024 void heapSort ( Rank lo, Rank hi ); //堆排序(稍後結合完全堆講解) * 0025 Rank partition ( Rank lo, Rank hi ); //軸點構造演算法 * 0026 void quickSort ( Rank lo, Rank hi ); //快速排序演算法 * 0027 void shellSort ( Rank lo, Rank hi ); //希爾排序演算法 * 0028 public: * 0029 // 建構函式 * 0030 Vector ( int c = DEFAULT_CAPACITY, int s = 0, T v = 0 ) //容量為c、規模為s、所有元素初始為v * 0031 { _elem = new T[_capacity = c]; for ( _size = 0; _size < s; _elem[_size++] = v ); } //s<=c * 0032 Vector ( T const* A, Rank n ) { copyFrom ( A, 0, n ); } //陣列整體複製 * 0033 Vector ( T const* A, Rank lo, Rank hi ) { copyFrom ( A, lo, hi ); } //區間 * 0034 Vector ( Vector<T> const& V ) { copyFrom ( V._elem, 0, V._size ); } //向量整體複製 * 0035 Vector ( Vector<T> const& V, Rank lo, Rank hi ) { copyFrom ( V._elem, lo, hi ); } //區間 * 0036 // 解構函式 * 0037 ~Vector() { delete [] _elem; } //釋放內部空間 * 0038 // 只讀訪問介面 * 0039 Rank size() const { return _size; } //規模 * 0040 bool empty() const { return !_size; } //判空 * 0041 Rank find ( T const& e ) const { return find ( e, 0, _size ); } //無序向量整體查詢 * 0042 Rank find ( T const& e, Rank lo, Rank hi ) const; //無序向量區間查詢 * 0043 Rank search ( T const& e ) const //有序向量整體查詢 * 0044 { return ( 0 >= _size ) ? -1 : search ( e, 0, _size ); } * 0045 Rank search ( T const& e, Rank lo, Rank hi ) const; //有序向量區間查詢 * 0046 // 可寫訪問介面 * 0047 T& operator[] ( Rank r ); //過載下標操作符,可以類似於陣列形式引用各元素 * 0048 const T& operator[] ( Rank r ) const; //僅限於做右值的過載版本 * 0049 Vector<T> & operator= ( Vector<T> const& ); //過載賦值操作符,以便直接克隆向量 * 0050 T remove ( Rank r ); //刪除秩為r的元素 * 0051 int remove ( Rank lo, Rank hi ); //刪除秩在區間[lo, hi)之內的元素 * 0052 Rank insert ( Rank r, T const& e ); //插入元素 * 0053 Rank insert ( T const& e ) { return insert ( _size, e ); } //預設作為末元素插入 * 0054 void sort ( Rank lo, Rank hi ); //對[lo, hi)排序 * 0055 void sort() { sort ( 0, _size ); } //整體排序 * 0056 void unsort ( Rank lo, Rank hi ); //對[lo, hi)置亂 * 0057 void unsort() { unsort ( 0, _size ); } //整體置亂 * 0058 int deduplicate(); //無序去重 * 0059 int uniquify(); //有序去重 * 0060 // 遍歷 * 0061 void traverse ( void (* ) ( T& ) ); //遍歷(使用函式指標,只讀或區域性性修改) * 0062 template <typename VST> void traverse ( VST& ); //遍歷(使用函式物件,可全域性性修改) * 0063 }; //Vector * *
構造方法
public void copyFrom(Collection<T> A, int lo, int hi){//泛型:https://www.cnblogs.com/coprince/p/8603492.html T[] _elem = new T[2 * (hi - lo)];//分配空間 int _size = 0; while(lo < hi){ _elem[_size++] = A[lo++]; } } * * public Vector(Collection<? extends E> c) { * //將集合Collection轉化為Object陣列elementData。如果c為空,那麼就會報空指標異常 * elementData = c.toArray(); * elementCount = elementData.length; * //將c中的元素拷貝到elementData中 * if (elementData.getClass() != Object[].class) * elementData = Arrays.copyOf(elementData, elementCount, Object[].class); * }
擴充與縮減
對於靜態空間,在新增元素時會出現上溢和下溢,因此需要對空間進行動態管理
擴容
容量遞增的策略:每次增加為原來的兩倍(只是表示意思,下面程式碼並沒有全寫上,可能是錯的)
//二倍增容 public void expand(int _size,int _capacity,int DEFAULT_CAPACITY,Collection<T> oldElem){ if (_size < _capacity){ return; } _capacity = Math.max(_capacity,DEFAULT_CAPACITY); T[] _elem = new T[_capacity <<= 1]; for (int i = 0; i < _size;i++){ _elem[i] = oldElem[i]; } } /**vertor原始碼上的 * 增加此向量的容量(如有必要),以確保其至少能夠儲存最小容量引數指定的元件數。 * 如果當前陣列的容量小於minCapacity,那麼就增加容量,增加陣列長度 * 新陣列的長度等於原陣列的長度加上增量capacityIncrement。 * 如果增加capacityIncrement小於等於0,那麼就自動擴增為原來二倍。 * 如果擴增為原來的二倍還是比minCapacity小,那麼就將minCapacity作為Object陣列的長度。 */ public synchronized void ensureCapacity(int minCapacity) { if (minCapacity > 0) { modCount++; ensureCapacityHelper(minCapacity); } } private void ensureCapacityHelper(int minCapacity) { //minCapacity為實際向量中的元素需要的容量,如果minCapacity大於當前陣列長度,那麼就進行擴容 if (minCapacity - elementData.length > 0) grow(minCapacity); } private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; private void grow(int minCapacity) { //oldCapacity舊容量是Object陣列的長度 int oldCapacity = elementData.length; //如果增量capacityIncrement大於0,那麼新容量為舊容量加上增量,否則為舊容量的2倍 int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); //如果新容量小於實際需要的容量,就將實際需要的容量賦值給新容量 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //新容量比陣列的最大容量還大,就進行擴容為最大容量 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); //原先的資料域中逐一取出各項轉移到新的資料域中 elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { //如果實際需要的容量小於0就丟擲異常 if (minCapacity < 0) throw new OutOfMemoryError(); //實際容量如果比最大容量還大,那麼實際容量為Integer.MAX_VALUE,否則為Integer.MAX_VALUE - 8 return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
縮容
@Test
public void shrink(int _size,int _capacity,int DEFAULT_CAPACITY,Collection<T> oldElem){
if (_capacity < DEFAULT_CAPACITY << 1){//不致收縮到DEFAULT_CAPACITY以下
return;
}
if (_capacity < _size << 2){//以25%為界
return;
}
T[] _elem = new T[_capacity <<= 1];
for (int i = 0; i < _size;i++){
_elem[i] = oldElem[i];
}
}
致亂演算法
@Test
public void permute(Collection<T> A){
for (int i = A.size();i > 0;i--){
int math = (int)(Math.random()*A.size());
int swap = A[i - 1];
A[i - 1] = A[math];
A[math] = swap;
}
}
無序向量
元素及相關操作
- 訪問
- 區間刪除
public static int[] remove(int[] arr,int lo,int hi) { //刪除陣列中某一元素方法
while(hi < arr.length - 1){
arr[++lo] = arr[++hi];
}
arr = Arrays.copyOf(arr, arr.length-(hi - lo)); //減小陣列長度
return arr;
}
無序向量的查詢
public int find(Object e , int lo, int hi, Object[] _elem){
while(( lo < hi--)&&(e != _elem[hi]));
return hi;//若hi < lo,則意味著失敗;否則hi即命中元素的秩
}
public int remove(int lo, int hi, Object[] _elem){
if ( lo == hi){
return 0;
}
while( hi < _elem.length){
_elem[lo++] = _elem[hi++];
}
//shrink();
return hi - lo;
}
無序向量的唯一化
public int deduplicate(int _size ,Object[] _elem){
int oldsize = _size;
int i = 1;
while(i < _size){
(find(_elem[i],0,i,_elem)) ? i++ : remove(i);
}
return oldsize - _size;
}
遍歷
@Test
public void test3(){
Vector<String> t=new Vector<String>();
t.add("F");
t.add("o");
t.add("r");
t.add("e");
t.add("v");
t.add("e");
t.add("r");
//第一種
for (String string : t) {
System.err.print(string);
}
//第二種
t.forEach(new Consumer<String>() {
@Override
public void accept(String t) {
// TODO Auto-generated method stub
System.out.print(t);
}
});
//第三種
for (int i = 0; i < t.size(); i++) {
System.out.print(t.get(i));
}
//第四種
Iterator<String> it = t.iterator();
while (it.hasNext()) {
String string = (String) it.next();
System.err.print(string);
}
//第五種
Enumeration<String> enume = t.elements();
while(enume.hasMoreElements()){
System.out.print(enume.nextElement().toString());
}
}
有序向量:唯一性
有序程度
@Test
public int test1(){
int[] _elem = new int[]{1,5,6,9,12,63,2,49,31,67};
int n = 0;
for (int i = 1;i < _elem.length;i++){
if (_elem[i -1] > _elem[i]){
n++;
}
}
return n;
}
低效版
@Test
public void test2(){
int[] _elem = new int[]{1,5,5,5,6,9,9,9,31,49,49,52,63};
int oldsize = _elem.length;
int i = 0;
while(i < _elem.length - 1){
if (_elem[i] == _elem[i+1]){
_elem = remove(_elem,i,i+1);
}else {
i++;
}
}
System.out.println(Arrays.toString(_elem));
System.out.println(oldsize - _elem.length);
}
public static int[] remove(int[] arr,int lo,int hi) { //刪除陣列中某一元素方法
while(hi < arr.length - 1){
arr[++lo] = arr[++hi];
}
arr = Arrays.copyOf(arr, arr.length-(hi - lo)); //減小陣列長度
return arr;
}
高效辦
@Test
public void test3(){
int[] _elem = new int[]{1,5,5,5,6,9,9,9,31,49,49,52,63};
int i = 0;
int j = 0;
int oldsize = _elem.length;
while (++j < _elem.length){
if (_elem[i] != _elem[j]){
_elem[++i] = _elem[j];
}
}
_elem = remove(_elem, i,oldsize - 1);
System.out.println(Arrays.toString(_elem));
System.out.println(oldsize - 1 -i);
}
有序向量:查詢
二分法:簡單版
@Test
public void test1(){
int[] _elem = new int[]{1,5,5,5,6,9,9,9,31,49,49,52,63};
int lo = 0;
int e = 32;
int hi = _elem.length;
int mi = find3(_elem,e,lo,hi);
System.out.println(mi);
}
//簡單的二分法查詢
public int find1(int[] S,int e,int lo,int hi){
while (lo < hi){
int mi = (lo + hi) >>1;
if (e < S[mi]){
hi = mi;
}else if (S[mi] < e){
lo = mi + 1;
}else {
return mi;
}
}
return -1;
}
二分法:不考慮軸點
/**
* 二分法查詢:不算軸點
*/
public int find3(int[] S,int e,int lo,int hi){
while (lo < hi){
int mi = (lo + hi) >>1;
if (e < S[mi]){
hi = mi;
}else {
lo = mi +1;
}
}
if(S[--lo] == e){
return --lo;
}else {
return -1;
}
}
斐波那契查詢
/**
* 斐波那契數列查詢
* 向量的長度 n = fib(k) - 1,則可取mi = fib(k - 1) - 1,於是,前、後子向量的長度分別為
* fib(k-1)-1、 fib(k-2)-1;這裡並不要求向量的長度一定要正好==fib(n) - 1;只要
* 用最小的n滿足size<=fib(n) - 1即可
*/
public int find2(int[] S,int e,int lo,int hi){
while (lo < hi){
int n = 0;
while (hi - lo > fib(n) - 1){
n++;
}
int mi = lo + fib(n-1) - 1;
if (e < S[mi]){
hi = mi;
}else if (S[mi] < e){
lo = mi + 1;
}else {
return mi;
}
}
return -1;
}
public int fib(int n){
int f = 0;
int g = 1;
while(0 < n--){
g = g + f;
f = g - f;
}
return g;
}
插值查詢
不一定每次都固定的選取 mi 相對於 lo 和 hi 的值,而是可以根據具體值來動態的確定 mi 。
二分查詢 是對 n 的數值每次折半的話,那 插值查詢 實際上是對 n 的二進位制位寬度來做二分查詢。二分查詢的迭代次數,我們知道是 logn 的,而 長度是 logn 的,所以最後插值查詢的迭代次數就是 loglogn 的。
public int find4(int[] S,int e,int lo,int hi){
while (lo < hi){
int mi = lo + (hi - lo)*(e - S[lo]) / (S[hi] - S[lo]);
if (e < S[mi]){
hi = mi;
}else if (S[mi] < e){
lo = mi + 1;
}else {
return mi;
}
}
return -1;
}
排序
起泡排序
- 簡單版-考慮是否已經全部有序
@Test
public void test1(){
int[] _elem = {100, 836, 3236, 5, 16, 26, -3, 89, 69, 43};
int n = _elem.length;
sort4(_elem,0,n);
System.out.println(Arrays.toString(_elem));
}
/**
* 排列的時候考慮到過程中已經全部有序的情況,這裡是n--
*/
public void sort1(int[] A,int n) {
for (boolean sorted = false; sorted = !sorted; n--) {
for (int i = 1; i < n; i++) {
if (A[i - 1] > A[i]) {
int b = A[i - 1];
A[i - 1] = A[i];
A[i] = b;
sorted = false;
}
}
}
}
- 改進1-考慮是否後方區域性有序
/**
* 排列的時候針對亂序元素位於[0,根號n],考慮到過程中後方已經有序的情況,這裡是n-很多
*/
public void sort2(int[] A,int lo,int hi) {
while (lo < (hi = bubble1(A,lo,hi)));
}
public int bubble1(int[] A,int lo,int hi){//一趟掃描
int last = lo;
while (++lo < hi){
if (A[lo - 1] > A[lo]) {
last = lo;
int b = A[lo - 1];
A[lo - 1] = A[lo];
A[lo] = b;
}
}
return last;
}
- 改進2-考慮是否前方區域性有序
/**
* 排列的時候針對亂序元素位於[n-根號n,n],考慮到過程中前方已經有序的情況,這裡是n-很多
*/
public void sort3(int[] A,int lo,int hi) {
while ((lo = bubble2(A,lo,hi)) < hi );
}
public int bubble2(int[] A,int lo,int hi){//一趟掃描
int first = hi;
while (lo < --hi){
if (A[hi - 1] > A[hi]) {
first = hi - 1;
int b = A[hi - 1];
A[hi - 1] = A[hi];
A[hi] = b;
}
}
return first;
}
- 改進3-考慮前後不斷排除有序情況
public void sort4(int[] A,int lo,int hi) {
while ((lo = bubble2(A,lo,hi)) < (hi = bubble1(A,lo,hi)) );
}
選擇排序
public class choiceSort {
/**
* 選擇排序
*/
@Test
public void test2(){
int [] arr = {49,38,65,97,76,13,27,49};
selectSort(arr,arr.length);
}
public void selectSort(int [] arr,int n){
for (int i = 0; i < n - 1; i++) {
int index = i;
int j;
// 找出最小值得元素下標
for (j = i + 1; j < n; j++) {
if (arr[j] < arr[index]) {
index = j;
}
}
int tmp = arr[index];
arr[index] = arr[i];
arr[i] = tmp;
}
}
}
歸併排序
public class MergeSort {
public static void main(String []args){
int[] arr = {100, 836, 3236, 5, 16, 26, -3, 89, 69, 43};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static void sort(int []arr){
int []temp = new int[arr.length];//在排序前,先建好一個長度等於原陣列長度的臨時陣列,避免遞迴中頻繁開闢空間
sort(arr,0,arr.length-1,temp);
}
private static void sort(int[] arr,int left,int right,int []temp){
if(left<right){
int mid = (left+right)/2;
sort(arr,left,mid,temp);//左邊歸併排序,使得左子序列有序
sort(arr,mid+1,right,temp);//右邊歸併排序,使得右子序列有序
merge(arr,left,mid,right,temp);//將兩個有序子數組合並操作
}
}
private static void merge(int[] arr,int left,int mid,int right,int[] temp){
int i = left;//左序列指標
int j = mid+1;//右序列指標
int t = 0;//臨時陣列指標
while (i<=mid && j<=right){
if(arr[i]<=arr[j]){
temp[t++] = arr[i++];
}else {
temp[t++] = arr[j++];
}
}
while(i<=mid){//將左邊剩餘元素填充進temp中
temp[t++] = arr[i++];
}
while(j<=right){//將右序列剩餘元素填充進temp中
temp[t++] = arr[j++];
}
t = 0;
//將temp中的元素全部拷貝到原陣列中
while(left <= right){
arr[left++] = temp[t++];
}
}
}