常用的查找與排序算法

                                                                           常用的查找與排序算法

目錄

工具 

1.生成隨機數組

2.測試排序算法的運行時間

3.打印輸出

排序

1.冒泡排序

原理

實現

複雜度

2.選擇排序

原理

實現

複雜度

3.插入排序

原理

實現

4.歸併排序

原理

複雜度分析

實現

優化:

結果

5. 堆排序

原理:

堆的基本屬性:

構建堆(插入新元素)

取出最大元素

查找:

1.順序查找

原理

實現

2.折半查找

原理:

實現:

3.二叉查找樹

原理

實現


 

  • 工具 

1.生成隨機數組

  • rand() 的內部實現是用線性同餘法做的,它不是真的隨機數,因其週期特別長,故在一定的範圍裏可看成是隨機的。
  • rand() 返回一隨機數值的範圍在 0RAND_MAX 間。RAND_MAX 的範圍最少是在 32767 之間(int)。用 unsigned int 雙字節是 65535,四字節是 4294967295 的整數範圍。0~RAND_MAX 每個數字被選中的機率是相同的。
  • rand() 產生的是僞隨機數字,系統默認的隨機數種子爲 1。每次執行時是相同的; 若要不同, 用函數 srand() 初始化它。
void generateArr(int arr[],int n,int rangL,int rangR)
{
    //隨機數種子
    srand(time(NULL));
    for(int i=0;i<n;i++)
        arr[i]=rand()%(rangR-rangL-1);
}
int main()
{
    std::cout<<"please input num: "<<std::endl;
    int n;
    cin>>n;
    int arr[n];
    generateArr(arr,n,5,100);
    for(int i=0;i<n;i++)
    {
        std::cout<<arr[i]<<" ";
    }
    std::cout<<std::endl;
    return 0;

}

2.測試排序算法的運行時間

void test_sort(string sort_name,void(*sort)(int [],int),int arr[],int n)
{
    clock_t star_time=clock();
    sort(arr,n);
    clock_t end_time=clock();
    std::cout<<sort_name<<" time: "<<double(end_time-star_time)/CLOCKS_PER_SEC<<std::endl;
}

3.打印輸出

void PrintArray(int *arr,int n)
{
	for(int i=0;i<n;i++)
	{
		cout<<arr[i]<<" ";
	}
	cout<<endl;
}
  • 排序

1.冒泡排序

  • 原理

每一輪,每兩個元素比較大小並進行交換,直到這一輪當中最大(最小)的元素被放置在數組的尾部,然後不斷地重複這個過程,直到所有元素都排好位置。其中,核心操作就是元素相互比較。

  • 實現

void bubble_sort(int arr[],int n)
{
	for(int i=0;i<n-1;i++)
	{
		for(int j=0;j<n-1-i;j++)
		{
			if(arr[j+1]<arr[j])
			{
				swap(arr[j+1],arr[j]);
			}
		}
	}
 } 
  • 優化版本: 判斷前一輪中是否進行過交換,如果沒有,則表示已經有序了;否則,爲無序的。
void BubbleSort2(int* num,int n)
{
    int temp;
    int flag=1;//無序標誌位
    for(int i=0;i<n-1&&flag==1;i++)
    {
        flag=0;
        for(int j=0;j<n-1-i;j++)
        {
            if(num[j]>num[j+1])
            {
                temp=num[j];
                num[j]=num[j+1];
                num[j+1]=temp;
                flag=1;
            }
        }
    }
}
  • 複雜度

  • 最好的情況:數組已排序好

需要進行n−1次的比較,兩兩交換次數爲O(1),時間複雜度是O(n)。

  • 最壞的情況:數組逆序排列

需要進行 n(n-1)/2 次比較,時間複雜度是 O(n^2)。

  • 一般情況:

平均時間複雜度是 O(n^2)。

2.選擇排序

  • 原理

第一次從待排序的數據元素中選出最小(或最大)的一個元素,存放在序列的起始位置,然後再從剩餘的未排序元素中尋找到最小(大)元素,然後放到已排序的序列的末尾。以此類推,直到全部待排序的數據元素的個數爲零。

  • 實現

void select_sort(int arr[],int n)
{
	int mindex;
	for(int i=0;i<n;i++)
	{
		mindex=i;
		for(int j=i+1;j<n;j++)
		{
			if(arr[mindex]>arr[j])
			{
				mindex=j;
			}
		}
		swap(arr[mindex],arr[i]);	
	}
}
  • 複雜度

  • 選擇排序是一種簡單直觀的排序算法,無論什麼數據進去都是 O(n²) 的時間複雜度。所以用到它的時候,數據規模越小越好。
  • 唯一的好處可能就是不佔用額外的內存空間了吧。

3.插入排序

  • 原理

  • 實現

template <typename T>
void sort_insert(T arr[],int n)
{
    for(int i=1;i<n;i++)
    {
        for(int j=i;j>0;j--)
            if(arr[j]>arr[j-1])
                swap(arr[j],arr[j-1]);
            else
                break;
    }
}
  • 優化版本:
template <typename T>
void sort_insert_1(T arr[],int n)
{
    for(int i=1;i<n;i++)
    {
        T e=arr[i];
        int j;//終止的位置
        for(j=i;j>0;j--)
        {
            if(arr[j-1]>e)
                arr[j]=arr[j-1];
            else
                break;
        }
        arr[j]=e;
    }
}

4.歸併排序

  • 原理

歸併排序(merge sort):利用歸併的思想實現的排序方法,該算法採用經典的分治(divide-and-conquer)策略(分治法將問題分(divide)成一些小的問題然後遞歸求解,而治(conquer)的階段則將分的階段得到的各答案"修補"在一起,即分而治之)。

歸併排序分爲三個過程:

  1. 將數列劃分爲兩部分(在均勻劃分時時間複雜度爲  );
  2. 遞歸地分別對兩個子序列進行歸併排序;
  3. 合併兩個子序列。

具體實現:

定義 mergeSort(nums, l, r) 函數表示對 nums 數組裏 [l,r]的部分進行排序,整個函數流程如下:

  1. 遞歸調用函數 mergeSort(nums, l, mid) 對 nums 數組裏 [l,mid]部分進行排序。
  2. 遞歸調用函數 mergeSort(nums, mid + 1, r) 對 nums 數組裏 [mid+1,r]部分進行排序。
  3. 此時 nums 數組裏 [l,mid][mid+1,r]兩個區間已經有序,我們對兩個有序區間線性歸併即可使 nums 數組裏 [l,r] 的部分有序。
  4. 我們對nums[l,r]裏的數據進行復制一份,構建一個輔助數組,存在l的偏移量。
  5. 由於兩個區間均有序,所以我們維護兩個指針 i和 j表示當前考慮到 [l,mid]裏的第 i個位置和 [mid+1,r]的第 j個位置。
  6. 如果 nums[i] < nums[j] ,那麼我們就將 nums[i]放入數組 nums[l+k]中並讓 i += 1 ,即指針往後移。否則我們就將 nums[j]放入數組nums[l+k] 中並讓 j += 1 。如果有一個指針已經移到了區間的末尾,那麼就把另一個區間裏的數按順序加入數組nums中即可。那麼整個歸併過程結束後 [l,r]即爲有序的

注:圖1來源於五分鐘學算法

  • 複雜度分析

來源於leetcode 912

  • 實現

  • 自頂向下
void _merge(int arr[],int l,int mid,int r)
{
	//分配大小爲r-l+1的輔助數組 
	int aux[r-l+1];
	for(int i=l;i<=r;i++)
	{
		//偏移量爲l 
		aux[i-l]=arr[i];
	}
	int i=l,j=mid+1;
	for(int k=l;k<=r;k++)
	{
		//左邊用盡,取右邊的元素 
		if(i>mid)
		{
			arr[k]=aux[j-l];
			j++;
		}
		//右邊用盡,取左邊的元素 
		else if(j>r)
		{
			arr[k]=aux[i-l];
			i++;
		}
		//右邊元素大於左邊,取左邊 
		else if(aux[i-l]<aux[j-l])
		{
			arr[k]=aux[i-l];
			i++;
		}
		//左邊元素大於等於右邊,取右邊 
		else
		{
			arr[k]=aux[j-l];
			j++;
		}			
	}
	
}
void _merge_sort(int arr[],int l,int r)
{
	if(l>=r)
		return;
	int mid=l+(r-l)/2;
	//歸併排序[l,mid] 
	_merge_sort(arr,l,mid);
	//歸併排序[mid+1,r] 
	_merge_sort(arr,mid+1,r);
	//歸併 
	_merge(arr,l,mid,r);		
}
void merge_sort(int arr[],int n)
{
	//範圍[l,r] 
	_merge_sort(arr,0,n-1);
} 
int main()
{
	int n=10;
	int *arr=new int[n];
	generateRandomArray(arr,n,10,50);
	PrintArray(arr,n);
	merge_sort(arr,n);	
	PrintArray(arr,n);
//	test_sort("select_sort",select_sort,arr,n);
} 
  • 優化:

1._merge之前判斷是否有序

  • [l,mid],[mid+1,r]兩個區間的元素都爲有序的,如果nums[mid+1]>nums[mid],那麼[l,r]部分已經有序,就可以不用進行歸併操作。

 

2.對小規模子數組使用插入排序

  • 插入排序對於近乎有序的數組,時間複雜度爲O(n)級別。

void insert_sort_3(int arr[],int l,int r)
{
	for(int i=l;i<=r;i++)
	{
		int e=arr[i];
		int j;
		for(j=i;j>l;j--)
		{
			if(arr[j-1]>e)
				arr[j]=arr[j-1];
			else
				break;				
		}
		swap(arr[j],e);
	}
}
  • 自頂向上

歸併:從子序列長度爲1(k)開始,進行兩兩歸併,得到2*k的有序序列;

循環:子序列長度爲2k開始,進行兩兩歸併,終止條件直到原數組遍歷完成。

void merge_sortBU(int arr[],int n)
{
	//k:當前子序列的長度 
	for(int k=1;k<n;k+=k)
		for(int low=0;low+k<n;low+=2*k)
			_merge(arr,low,low+k-1,min(low+k+k-1,n-1));
	
}
int main()
{
	int n=10;
	int *arr=new int[n];
	generateRandomArray(arr,n,0,20);
	PrintArray(arr,n);
	merge_sortBU(arr,n);	
	PrintArray(arr,n);
//	test_sort("select_sort",select_sort,arr,n);
} 
  • 結果

 

5. 堆排序

  • 原理:

堆排序(Heapsort)是指利用這種數據結構所設計的一種排序算法。堆是一個近似完全二叉樹的結構,並同時滿足如下性質:

  • 堆中某個節點的值總是不大於或不小於其父節點的值;

  • 堆總是一棵完全二叉樹。

  • 大頂堆:每個結點的值都大於或等於其左右孩子結點的值;
  • 小頂堆:每個結點的值都小於或等於其左右孩子結點的值。

對於堆(一顆完全二叉樹)的表示, 我們採用順序結構對數據進行存儲,可以根據如下性質得到元素的下標(數組下標從1開始):

  • 如果2i小於n,則編號爲i的結點的左孩子編號爲2i,如果2i大於n,則該結點沒有左孩子。
  • 如果2i+1小於n,則編號爲i的結點的右孩子編號爲2i+1,如果2i+1大於n,則該結點沒有右孩子。
  • 編號爲count的結點,其父節點的編號爲:count/2; 

堆的基本屬性:

  •  data[],數組
  • count,當前存放的元素數量,初始化時爲0;
  • capacity,最大容量
#include<iostream>
#include<ctime>
#include <cstdlib>
#include<cmath>
using namespace std;
class MaxHeap{
public:
	MaxHeap(int maxsize)
	{
		//max size
		capacity=maxsize;
		data=new int[maxsize+1];
		count=0;
	}
	bool is_empty()
	{
		return count==0;
	} 
	int size()
	{
		return count;
	}
	
private:
	//存放的數據 
	int *data;
	//當前的size 
	int count;
	//最大容量 
	int capacity;
		
}; 

構建堆(插入新元素)

  • 在數組的末尾插上新的元素;
  • 數組的size:count++;
  • 調整元素,使其滿足堆的性質(shift up操作);

比如我們已經有一個最大堆,76,32,8,7,插入的新元素爲:40。

比較結點40和其父結點比較,使其滿足堆的性質,如果比父結點大,則交換,否則已經滿足最大堆了。

	void shiftup(int k)
	{
		while(k>1&&data[k]>data[k/2])
		{	
			swap(data[k],data[k/2]);
			k=k/2;
		}
	}
	void insert(int num)
	{
		data[count+1]=num;
		count++;
		shiftup(count);		
	}

 取出最大元素

  • 取出data[0](堆的最大元素);
  • 交換data[0],data[count]
  • count減一;
  • 調整元素,使其滿足堆的性質(shift down操作)
	int extractMax()
	{
		int temp=data[1];
		swap(data[1],data[count]);
		count--;
		shift_down(1);
		return temp; 
	}
	void shift_down(int k)
	{
		while(2*k<=count)
		{		
			int j=2*k;
			if(j+1<=count&&data[j+1]>data[j])
				j++;
			if(data[k]>=data[j])
				break;
			swap(data[k],data[j]);
			k=j;
		}
    }

查找:

1.順序查找

  • 原理

  • 思想: 

順序查找就是在關鍵字集合中找出與給定的關鍵字相等的元素。

  • 步驟:

(1)從文件的第一個記錄開始,將每個記錄的關鍵字與給定的關鍵字比較
(2)如果查找到某個記錄的關鍵字等於 key,則查找成功,返回該記錄在文件中的位置;如果所有的記錄都進行了比較,仍未找到與 k 相等的記錄,則給出 0,表示查找失敗。

  • 時間複雜度:O(n)
  • 實現

#include <iostream>
using namespace std;
typedef struct student
{
    unsigned  int id;
    char name[10];
    int score;
}Student;
int searchinfo(Student *stu,int num,int id)
{
    for(int i=0;i<num;i++)
    {
        if(stu[i].id==id)
            return i;
    }
    return -1;
}
int main()
{
    Student stu[4]={{1004,"TOM",100},
                    {1002," LILY",95},
                    {1001,"ANN",93},
                    {1003,"luCY",98}};
    int address;
    address=searchinfo(stu,4,1001);
    std::cout<<"ID: "<<stu[address].id<<std::endl;
    std::cout<<"name: "<<stu[address].name<<std::endl;
    std::cout<<"score: "<<stu[address].score<<std::endl;
    return  0;
}

 

 2.折半查找

  • 原理:

  • 思想: 

二分查找的思想是利用分治法,逐漸將查找的數據集範圍縮小。

  • 具體過程:

將待查的關鍵字 k 與當前查找範圍內位置居中的關鍵字比較,如果相等,則查找成功,返回被查到記錄在文件中的位置;如果 k 小於當前居中記錄的關鍵字,則對當前查找範圍的前半部分重複上述過程,否則到當前查找範圍的後半部分重複上述過程。如果查找失敗返回NULL。

Image result for 二分查找 gif

  • 時間複雜度:O(log2n)

  • 實現:

  • 閉區間:[l,r]  
#include<iostream>
using namespace std;

int Binary_search(int arr[],int n,int target)
{
	int l=0,r=n-1;//在[l,r]閉區間搜索target 
	while(l<=r)//當l==r時,區間依然有效 
	{
		int mid=l+(r-l)/2;
		if(arr[mid]==target)
		{
			return mid+1;	
		}
		else if(arr[mid]<target)
		{
			l=mid+1;//在[mid+1,r]閉區間搜索target	
		}
		else
		{
			r=mid-1;//在[l,mid-1]閉區間搜索target
		} 	
	}
	return -1;
 }
 int main()
 {
 	int arr[5]={0,1,5,6,7};
 	int index=Binary_search(arr,5,10);
 	cout<<index<<endl;
 	return 0;
  } 
  • 半開區間:[l,r)
#include<iostream>
using namespace std;

int Binary_search(int arr[],int n,int target)
{
	int l=0,r=n;//在[l,r)區間搜索target 
	while(l<r)//當l==r時,區間無效 
	{
		int mid=l+(r-l)/2;
		if(arr[mid]==target)
		{
			return mid+1;	
		}
		else if(arr[mid]<target)
		{
			l=mid+1;//在[mid+1,r)區間搜索target	
		}
		else
		{
			r=mid;//在[l,mid-1)區間搜索target
		} 	
	}
	return -1;
 }
 int main()
 {
 	int arr[5]={0,1,5,6,7};
 	int index=Binary_search(arr,5,7);
 	cout<<index<<endl;
 	return 0;
  } 
  • 注意:

 二分查找的區間問題。

3.二叉查找樹

  • 原理

  • 簡介:

二叉查找樹是一種具有良好排序查找性能的二叉樹數據結構。二叉查找樹支持多種基本操作,包括查找、按序遍歷、求最大值和最小值、查找前驅結點和後繼結點、插入和刪除結點等。一般將它用於查找字典或優先隊列結構。這些操作在二叉查找樹上的平均執行時間是 O( log2 n )。

  • 定義:

1. 它是一棵二叉樹,如果根結點的左子樹不空,則左子樹中所有的結點值均小於根結點。
2. 如果根結點的右子樹不爲空,則右子樹中所有的結點值均大於根結點。
3. 二叉查找樹的每一棵子樹也是一棵二叉查找樹。

  • 實現

#include<iostream>
using namespace std;
typedef struct node{
	int data;
	struct node *left;
	struct node *right;
}Node;

void create_tree(Node *Tree, int data)
{
	//初始化新結點 
	Node* new_node=new Node;
	new_node->data=data;
	new_node->left=NULL;
	new_node->right=NULL; 
	
	Node* cur=Tree;	
	while(cur)
	{
		
		if(cur->data<data)
		{	
			//右節點爲空,直接將新節點連接在右節點上 
			if(cur->right==NULL)
			{
	
				cur->right=new_node;
				return ;
			}
			//右節點不爲空,更新當前節點爲右節點 
			else
			{
				cur=cur->right;

			}
		}
		//同理 
		else
		{
			if(cur->left==NULL)
			{
				cur->left=new_node;
				return ;
			}
			else
			{
				cur=cur->left;
			}
		}
	}
}
void middle_search(Node* Tree)
{
	if(!Tree)
		return;
	middle_search(Tree->left);
	cout<<Tree->data<<" ";
	middle_search(Tree->right);
}
int main()
{
	Node* Tree=new Node;
	Tree->left=NULL;
	Tree->right=NULL;
	Tree->data=10;
	create_tree(Tree,12);
	create_tree(Tree,9);
	create_tree(Tree,50);
	create_tree(Tree,20);
	middle_search(Tree);
	return 0;
 } 

參考:

https://www.runoob.com/w3cnote_genre/algorithm

https://www.cnblogs.com/chengxiao/p/6129630.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章