排序:
以下排序都是按从小到大的顺序编写的;
1)直接插入排序:
时间复杂度O(n*n)
//从小到大
//直接插入排序:取出一个比,比完再插入
void InsertSort(int* arr, size_t n)
{
assert(arr);
for(size_t i = 0 ; i < n-1; i++)
{
int temp = arr[i+1];
int j = i;
for(; j >= 0; --j)
{
if(arr[j] > temp)
{
arr[j+1] = arr[j];
}
else
break;
}
arr[j+1] = temp;
}
}
2)希尔排序:预排序+插入排序
类似于插入排序的,只不过是跨步比(+gap),是插入排序的一种优化;
预排序:尽可能的将大数放后面,小数放前面,不是完全有序的,当gap越小越接近于有序,当gap==1时就是插入排序,当gap>1是希尔排序;
//希尔排序
//时间复杂度:O(n)< O <O(n*n)
void shellSort(int* arr,size_t n)
{
assert(arr);
int gap = 3;
while(gap > 1)
{
gap = gap/3 + 1;
for(size_t i = 0; i < n-gap; i++)
{
int temp = arr[i+gap];
int j = i;
for(; j >= 0; j -= gap)
{
if(arr[j] > temp)
{
arr[j+gap] = arr[j];
}
else
break;
}
arr[j+gap] = temp;
}
}
}
3)选择排序:
选择排序是最坏的排序;
时间复杂度是:O(n*n)
//选择排序----最坏的排序,这里我们一次性选出最大最小值
void selectSort(int* arr,size_t n)
{
size_t begin = 0;
size_t end = n-1;
while(begin < end)
{
int min = begin;
int max = begin;
for(size_t i = begin+1; i <= end; i++)
{
if(arr[i] < arr[min])
{
min = i;
}
if(arr[i] > arr[max])
{
max = i;
}
}
swap(arr[min],arr[begin]);
//如果此时最大值就是begin下标的那个值,
//经过上一步的交换,最大值被换到了下标是min的位置上去了
if(begin == max)
{
max = min;
}
swap(arr[max],arr[end]);
++begin,--end;
}
}
4)堆排序:分为大堆和小堆
时间复杂度是:O(n*lgn)
//建大堆,先把最大的值放在上面,然后再让最大值和最后一个值交换,然后再进行向下调整,再把第二大放在最上面,依次类推;
//从小到大
//堆排序----建大堆
void AdjustDown(int* arr,size_t n,int father)
{
int childL = 2*father+1;
while(childL < n)
{
if((childL+1) < n && arr[childL+1] > arr[childL])
{
childL++;
}
if(arr[father] < arr[childL])
{
swap(arr[father],arr[childL]);
father = childL;
childL = 2*father+1;
}
else
break;
}
}
void HeapSort(int* arr,size_t n)
{
assert(arr);
for(int i = (n-2)/2; i >= 0; --i)
{
AdjustDown(arr,n,i);
}
//目前最大值在顶层
int end = n-1;
while(end > 0)
{
swap(arr[0],arr[end]);
//调整
AdjustDown(arr,end,0);
--end;
}
}
5)冒泡排序;
这应该是我们最熟悉的排序吧,不做特别说明直接见代码:
//冒泡排序
void BubbleSort(int* arr,size_t n)
{
assert(arr);
for(size_t j = 0; j < n-1; j++)
{
//单趟
int heap = 0;
for(size_t i = 0; i < n-1; i++)
{
if(arr[i] > arr[i+1])
{
heap = 1;
swap(arr[i],arr[i+1]);
}
}
if(heap == 0)
{
break;
}
}
}
6)快速排序:—-递归过程
A.左右指针法:
左边找大于key的,右边找小于key的
key就是最右边那个值(实际值);
begin和end是下标
//左右指针法
int PartSort(int* arr,int begin,int end)
{
int right = end;
int key = arr[end];
while(begin < end)
{
//先判断左指针
while(arr[begin] < key && begin < end)
{
++begin;
}
//再判断右指针
while(arr[end] >= key && begin < end)
{
--end;
}
//交换两个指针找到的值
swap(arr[begin],arr[end]);
}
swap(arr[begin],arr[right]);
return begin;
}
B.挖坑法:(和左右指针法类似,只不过这次是将选中的那个数挖走交换)
//挖坑法
int PartWSort(int* arr,int begin,int end)
{
int newkey = GetMidKey(arr,begin,end);
/*int pit = end;*/
swap(arr[newkey],arr[end]);
int key = arr[end];
while(begin < end)
{
while(arr[begin] < key && begin < end)
{
++begin;
}
/*if(begin < end)
{
arr[pit] = arr[begin];
pit = begin;
}*/
arr[end] = arr[begin];
while(arr[end] >= key && begin < end)
{
--end;
}
/*if(begin < end)
{
arr[pit] = arr[end];
pit = end;
}*/
arr[begin] = arr[end];
}
/*arr[pit] = key;*/
arr[end] = key;
return begin;
}
C.前后指针法:
一前一后两个指针:
cur走到9的时候,9>key,++cur(多走一步),所以++prev走到9的位置,prev != cur,所以交换5和9:
//前后指针法
int PartPBSort(int* arr,int begin,int end)
{
assert(arr);
int indexKey = GetMidKey(arr,begin,end);//返回的是下标
/*int key = arr[end];*/
swap(arr[indexKey],arr[end]);
int key = arr[end];
int cur = begin;
int prev = cur - 1;
while(cur < end)
{
if(arr[cur] < key && ++prev != cur)
{
swap(arr[prev],arr[cur]);
}
++cur;
}
swap(arr[end],arr[++prev]);
return prev;
}
void QuickSort(int* arr,int begin,int end)
{
assert(arr);
if(begin < end)
{
int div = PartWSort(arr,begin,end);
/* int div = PartPBSort(arr,begin,end);*/
QuickSort(arr,begin,div-1);
QuickSort(arr,div+1,end);
}
}
但是快速排序也有缺陷:
Eg:2 3 230 32 2 3 3 3 32 2 2 3 3 233
以上就无法用快排,降低了快速排序的性能;如果每次选中的数据是最大/最小值,那么就是N*N的复杂度;
所以要避免每次选取key的时候选择最大、最小值特有以下解决方法:
① 三数取中法:每次都给出最左边和最右边的值(下标),然后求中间值(下标),再比较三个数大小(实际值),找出中间值(实际值)的作为key,返回的是下标;
//三数取中法---取键值
int GetMidKey(int* arr,int begin,int end)
{
assert(arr);
int mid = begin + ((end - begin) >> 1);
if(arr[begin] < arr[mid])
{
if(arr[begin] > arr[end])
{
return begin;
}
else if(arr[mid] < arr[end])
{
return mid;
}
else
{
return end;
}
}
else
{
//arr[begin] >= arr[mid]
if(arr[mid] > arr[end])
{
return mid;
}
else if(arr[begin] > arr[end])
{
return end;
}
else
{
return begin;
}
}
}
② 小区间优化快排法:其实就是当你将要排序的小区间的长度小于某个固定值(这里我们设为10)时,采用插入排序法,如果不是就还是采用快排的方式排序;
//小区间优化法快排
void MinizoneQuickSort(int* arr,int begin,int end)
{
assert(arr);
int size = end - begin;
if(size <= 0)
{
return;
}
else if(size < 10)
{
InsertSort(arr+begin,size+1);
}
else
{
int div = PartSort(arr,begin,end);
MinizoneQuickSort(arr,begin,div-1);
MinizoneQuickSort(arr,div+1,end);
}
}
③ 非递归快排;(利用stack的结构)
其实思想和快排的递归思想一致,只不过我们需要创建一个栈stack,来模拟实现递归过程的压栈过程,我们压下标即可;
//非递归快排
void QuickSortNonR(int* arr,int begin,int end)
{
assert(arr);
stack<int> _stack;
if(begin < end)
{
_stack.push(end);//放进去的是下标
_stack.push(begin);
}
while(!_stack.empty())
{
int left = _stack.top();
_stack.pop();
int right = _stack.top();
_stack.pop();
int div = PartSort(arr,left,right);
if(div+1 < right)
{
_stack.push(right);
_stack.push(div+1);
}
if(left < div-1)
{
_stack.push(div-1);
_stack.push(left);
}
}
}
7)归并排序:
思想:递归切分,先将begin和end区间逐个切分到一个区间就一个数,然后开始回退比较,这样生成的左右两边有序小区间排序,生成一个大一点的有序小区间后,继续向上回退,再左右两边排序,这样一直回退到顶端;
这种思想适合大量数据排序,时间复杂度是N*lgN,空间复杂度为O(N);这是一种外排序——–>磁盘上排序(如果内存放不下就放在磁盘上)
//归并排序----外排序----磁盘
void _Sort(int* arr,int* temp,int begin1,int end1,int begin2,int end2)
{
int start = begin1;
int finish = end2;
int index = begin1;
while(begin1 <= end1 && begin2 <= end2)
{
if(arr[begin1] > arr[begin2])
{
temp[index++] = arr[begin2++];
}
else
{
temp[index++] = arr[begin1++];
}
}
while(begin1 <= end1)
{
temp[index++] = arr[begin1++];
}
while(begin2 <= end2)
{
temp[index++] = arr[begin2++];
}
memcpy(arr+start,temp+start,sizeof(int)*(finish - start + 1));
}
//分成小区间
void _MergeSort(int* arr,int* temp,int begin,int end)
{
if(begin >= end)
{
return;
}
int mid = begin + ((end - begin) >> 1);
_MergeSort(arr,temp,begin,mid);
_MergeSort(arr,temp,mid+1,end);
_Sort(arr,temp,begin,mid,mid+1,end);
}
void MergeSort(int* arr,int n)
{
assert(arr);
int* temp = new int[n];//先创建一个临时变量,并开辟空间
_MergeSort(arr,temp,0,n-1);
delete [] temp;//自己申请的空间用完以后要自己释放
}
8)并查集:只针对下标(缺陷)
将N个不同的元素分成一组不相交的集合;
开始时每个元素都是一个集合,然按照规律将两个集合进行合并
也就是说会将与集合中某些元素相关的多个小集合合并成大集合,比如说朋友圈,{1,3},{2,3},{3,4},{5,6}——->{1,2,3,4},{5,6}这样的,我们同通过如下方法分配他们之间的关系:
分析过程如下:
//并查集
class UnionFindSet
{
protected:
vector<int> _ufs;
public:
UnionFindSet(size_t n)
{
_ufs.resize(n,-1);
}
//注:每次放入的一组数据一定是一个集合;
void SetUnion(int x1,int x2)
{
int root1 = _FindRoot(x1);
int root2 = _FindRoot(x2);
if(root1 != root2)
{
if(_ufs[root1] == -1)
{
swap(root1,root2);
}
_ufs[root1] += _ufs[root2];
_ufs[root2] = root1;
}
}
//判断是否是一个集合
bool IsOneUnion(int x1,int x2)
{
return _FindRoot(x1) == _FindRoot(x2);
}
//判断有几个根
int GetRootCount()
{
size_t count = 0;
for(size_t i = 0; i < _ufs.size(); i++)
{
if(_ufs[i] < 0)
{
++count;
}
}
return count;
}
private:
int _FindRoot(int x)
{
int root = x;
if(_ufs[x] >= 0)
{
root = _ufs[x];
}
return root;
}
};
int friends(int n,int m,int r[][2])
{
UnionFindSet ufs(n+1);
for(size_t i = 0; i < m; i++)
{
ufs.SetUnion(r[i][0],r[i][1]);
cout<<ufs.IsOneUnion(r[i][0],r[i][1])<<" ";
}
return ufs.GetRootCount()-1;//去掉_ufs[0]
}
void TestUnion()
{
int n = 6;
const int m = 4;
int r[m][2] = {{1,3},{2,3},{3,4},{5,6}};
cout<<friends(n,m,r)<<endl;
}