数据结构和算法
前言
数据结构(data structure)是就散集中存储,组织数据的方式;
数据结构是一种逻辑关系,包含了逻辑关系,存储关系,操作关系
不同的数据结构,根据业务的实际需要可以自行设计;比如计算机网络依赖于路由表,数据库使用B树封装;
核心目的:实现增删改查
常见的数据结构
栈(stack):线性表,先进后出的结构
队列(queue):线性表,先进先出的结构
数组(array):内存连续固定大小的数据集合;
链表(link list):内存不连续,大小不固定的数据结构;首尾相接;
树(tree):非线性结构,一般使用二叉树,一个根节点,两个子节点;
图(graph):非线性结构,数据结点一般称为顶点,而边是顶点的有序偶对;
堆(heap):是一种特殊的树形数据结构,一般讨论的都是二叉堆;
散列(hash table):散列源自于散列函数,其思想是如果在结构中存在关键字和T相等的记录,那么必定在F(T)的存储位置可以找到该记录,这样就可以不用进行比较操作而直接取得所查的记录;
经典排序算法
排序算法分类
比较排序
- 交换排序:冒泡排序,快速排序
- 插入排序:简单插入排序,希尔排序
- 选择排序:简单选择排序,堆排序
- 归并排序:二路归并排序,多路归并排序
非比较排序
- 计数排序
- 桶排序
- 基数排序
算法复杂度图
稳定:如果a在前,b在后,且a=b,排序后a在前,b在后,说明排序稳定
不稳定:如果a在前,b 在后,且a=b,排序后b在前,a在后,说明排序算法不稳定;
时间复杂度:排序时对数据操作的次数,比如遍历;
空间复杂度:内存开销
冒泡排序
算法描述:
- 比较相邻的元素,升序,如果前比后大,交换二者;
- 对每一对相邻的元素做重复的工作,从头至尾;
- 直到所有的元素排序满足要求;
#include <iostream>
int MyArray[] = {8,7,1,0,9,4,3,5,6,2};
`
void Sort_Bubble(int array[], int size)
{
int length = size;
std::cout<<"length array ="<<length<<std::endl;
//做冒泡排序
for(int i=0; i<=9; i++)
{
std::cout<<array[i]<<" ";
}
std::cout<<" "<<std::endl;
for (size_t i = 0; i < length; i++)
{
for (size_t j = 0; j < length -1 -i; j++)
{
//升序排列;
if(array[j] > array[j+1])
{
int temp = array[j+1];
array[j+1]=array[j];
array[j] = temp;
}
}
}
for (size_t i = 0; i < length; i++)
{
/* code */
std::cout<<array[i]<<" ";
}
}
int main()
{
// Sort_Bubble();
Sort_Bubble(MyArray, int(sizeof(MyArray)/sizeof(MyArray[0])));
return 0;
}
sort_test.cpp:85:2: warning: no newline at end of file
length array =10
8 7 1 0 9 4 3 5 6 2
0 1 2 3 4 5 6 7 8 9
[Done] exited with code=0 in 3.195 seconds
选择排序
选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
算法描述
n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果
初始状态:无序区为R[1…n],有序区为空;
第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R(i…n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1…i]和R[i+1…n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
n-1趟结束,数组有序化了。
#include <iostream>
int MyArray[] = {8,7,1,0,9,4,3,5,6,2};
void (*pFunction)(int array[], int size);
void Sort_Selection(int array[], int size)
{
std::cout<<"array size = "<<size<<std::endl;
for(int i=0; i<=9; i++)
{
std::cout<<array[i]<<" ";
}
std::cout<<" "<<std::endl;
int minIndex=0;
for (size_t i = 0; i < size; i++)
{
/* 选择排序*/
minIndex = i;
for(size_t j =i+1; j<size ; j++)
{
//升序排序
if(array[j] < array[minIndex])
{
minIndex = j;
}
}
//交换最小的值到最前面;
int temp = array[i];
array[i] = array[minIndex];
array[minIndex] = temp;
}
for (size_t i = 0; i < size; i++)
{
/* code */
std::cout<<array[i]<<" ";
}
}
int main()
{
pFunction = &Sort_Selection;
pFunction(MyArray,int(sizeof(MyArray)/sizeof(MyArray[0])));
return 0;
}
sort_test.cpp:128:2: warning: no newline at end of file
array size = 10
8 7 1 0 9 4 3 5 6 2
0 1 2 3 4 5 6 7 8 9
[Done] exited with code=0 in 3.464 seconds
插入排序
插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
算法描述
一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:
1.从第一个元素开始,该元素可以认为已经被排序;
2.取出下一个元素,在已经排序的元素序列中从后向前扫描;
3.如果该元素(已排序)大于新元素,将该元素移到下一位置;
4.重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
5.将新元素插入到该位置后;
6.重复步骤2~5。
算法分析
插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
#include <iostream>
int MyArray[] = {8,7,1,0,9,4,3,5,6,2};
void (*pFunction)(int array[], int size);
void Sort_Insertion(int array[], int size)
{
std::cout<<"array size = "<<size<<std::endl;
for(int i=0; i<=9; i++)
{
std::cout<<array[i]<<" ";
}
std::cout<<" "<<std::endl;
int preIndex = 0;
int current = 0;
for(size_t i=1; i<size; i++)
{
preIndex = i-1; //注意起始点
current = array[i];
//循环比较,合适的位置插入;升序排序
while (preIndex >=0 && array[preIndex]>current)
{
/* code */
array[preIndex+1]=array[preIndex];
preIndex--;
}
array[preIndex +1] = current;
}
for (size_t i = 0; i < size; i++)
{
/* code */
std::cout<<array[i]<<" ";
}
}
int main()
{
// pFunction = &Sort_Selection;
pFunction = &Sort_Insertion;
pFunction(MyArray,int(sizeof(MyArray)/sizeof(MyArray[0])));
return 0;
}
sort_test.cpp:169:2: warning: no newline at end of file
array size = 10
8 7 1 0 9 4 3 5 6 2
0 1 2 3 4 5 6 7 8 9
[Done] exited with code=0 in 3.799 seconds
希尔排序
1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。
算法描述
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
1.选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
2.按增量序列个数k,对序列进行k 趟排序;
3.每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。
仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
希尔排序的核心在于间隔序列的设定。既可以提前设定好间隔序列,也可以动态的定义间隔序列。动态定义间隔序列的算法是《算法(第4版)》的合著者Robert Sedgewick提出的。
#include <iostream>
#include <cmath>
int MyArray[] = {8,7,1,0,9,4,3,5,6,2};
void (*pFunction)(int array[], int size);
void Sort_Shell(int array[], int size)
{
std::cout<<"array size = "<<size<<std::endl;
for(int i=0; i<size; i++)
{
std::cout<<array[i]<<" ";
}
std::cout<<"/n"<<std::endl;
for(size_t i=std::floor(size/2); i>0; i=std::floor(i/2))
{
for(size_t j= i; j<size; j++)
{
int index = j;
int current=array[j];
while(index -i >=0 && current < array[index -i])
{
array[index] = array[index -i];
index = index -i;
}
array[index] = current;
for(int i=0; i<=9; i++)
{
std::cout<<array[i]<<" ";
}
std::cout<<"/n"<<std::endl;
}
}
for(int i=0; i<size; i++)
{
std::cout<<array[i]<<" ";
}
std::cout<<"/n"<<std::endl;
}
int main()
{
// pFunction = &Sort_Bubble;
// pFunction = &Sort_Selection;
// pFunction = &Sort_Insertion;
pFunction = &Sort_Shell;
pFunction(MyArray,int(sizeof(MyArray)/sizeof(MyArray[0])));
return 0;
}
## 测试结果展示
array size = 10
8 7 1 0 9 4 3 5 6 2 /n
4 7 1 0 9 8 3 5 6 2 /n
4 3 1 0 9 8 7 5 6 2 /n
4 3 1 0 9 8 7 5 6 2 /n
4 3 1 0 9 8 7 5 6 2 /n
4 3 1 0 2 8 7 5 6 9 /n
1 3 4 0 2 8 7 5 6 9 /n
1 0 4 3 2 8 7 5 6 9 /n
1 0 2 3 4 8 7 5 6 9 /n
1 0 2 3 4 8 7 5 6 9 /n
1 0 2 3 4 8 7 5 6 9 /n
1 0 2 3 4 5 7 8 6 9 /n
1 0 2 3 4 5 6 8 7 9 /n
1 0 2 3 4 5 6 8 7 9 /n
0 1 2 3 4 5 6 8 7 9 /n
0 1 2 3 4 5 6 8 7 9 /n
0 1 2 3 4 5 6 8 7 9 /n
0 1 2 3 4 5 6 8 7 9 /n
0 1 2 3 4 5 6 8 7 9 /n
0 1 2 3 4 5 6 8 7 9 /n
0 1 2 3 4 5 6 8 7 9 /n
0 1 2 3 4 5 6 7 8 9 /n
0 1 2 3 4 5 6 7 8 9 /n
0 1 2 3 4 5 6 7 8 9 /n
[Done] exited with code=0 in 2.965 seconds
归并排序
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
算法描述
- 把长度为n的输入序列分成两个长度为n/2的子序列;
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
#include <iostream>
#include <cmath>
int MyArray[] = {8,7,1,0,9,4,3,5,6,2};
void (*pFunction)(int array[], int size);
void MergeMethod(int array[], int left, int middle, int right)
{
int *temp = new int(right - left +1);
int p1= left;
int p2=middle+1;
int i=0;
while(p1<=middle && p2<=right)
{
temp[i++]=array[p1]>array[p2] ? array[p2++]:array[p1++];
}
while(p1<=middle)
{
temp[i++] = array[p1++];
}
while(p2<=right)
{
temp[i++]=array[p2++];
}
for(int i=0;i<right - left +1; i++)
{
array[left+i]=temp[i];
}
delete []temp;
}
void MergeSort(int array[], int left, int right)
{
if(left < right)
{
int mid = left + ( right-left)/2;
MergeSort(array,left,mid);
for(int i=0; i<10; i++)
{
std::cout<<array[i]<<" ";
}
std::cout<<"/n"<<std::endl;
MergeSort(array,mid+1,right);
MergeMethod(array,left,mid, right);
}
}
void Sort_Merge(int array[], int size)
{
std::cout<<"array size = "<<size<<std::endl;
for(int i=0; i<size; i++)
{
std::cout<<array[i]<<" ";
}
std::cout<<"/n"<<std::endl;
if(size <2)
{
std::cout<<"the number is to small , no need do this sort method."<<std::endl;
}
MergeSort(array,0,size-1);
for(int i=0; i<size; i++)
{
std::cout<<array[i]<<" ";
}
std::cout<<"/n"<<std::endl;
}
int main()
{
// pFunction = &Sort_Bubble;
// pFunction = &Sort_Selection;
// pFunction = &Sort_Insertion;
// pFunction = &Sort_Shell;
pFunction = &Sort_Merge;
pFunction(MyArray,int(sizeof(MyArray)/sizeof(MyArray[0])));
return 0;
}
array size = 10
8 7 1 0 9 4 3 5 6 2 /n
8 7 1 0 9 4 3 5 6 2 /n
7 8 1 0 9 4 3 5 6 2 /n
1 7 8 0 9 4 3 5 6 2 /n
1 7 8 0 9 4 3 5 6 2 /n
0 1 7 8 9 4 3 5 6 2 /n
0 1 7 8 9 4 3 5 6 2 /n
0 1 7 8 9 3 4 5 6 2 /n
0 1 7 8 9 3 4 5 6 2 /n
0 1 7 8 9 3 4 5 6 2 /n
0 1 2 3 4 5 6 7 8 9 /n
[Done] exited with code=0 in 3.589 seconds
快速排序
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
算法描述
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
- 从数列中挑出一个元素,称为 “基准”(pivot);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
#include <iostream>
#include <cmath>
int MyArray[] = {8,7,1,0,9,4,3,5,6,2};
void (*pFunction)(int array[], int size);
void Fastsection(int array[], int left, int right)
{
//快速排序
if(left >= right)
return;
int i,j, base,temp;
i=left;
j=right;
base=array[left];
while(i<j)
{
//找到右边第一个小于基准参考的,并记录后跳出
while(array[j]>=base && i<j)
j--;
//找到左边第一个大于基准参考的,并记录后跳出
while(array[i]<=base && i<j)
i++;
if(i<j)
{
//交换左右哨兵发现的值
temp = array[i];
array[i]=array[j];
array[j]=temp;
}
}
array[left]=array[i];
array[i]=base;
for(int i=0; i<10; i++)
{
std::cout<<array[i]<<" ";
}
std::cout<<"/n"<<std::endl;
Fastsection(array,left,i-1);
Fastsection(array,i+1,right);
}
void Sort_Fast(int array[], int size)
{
std::cout<<"array size = "<<size<<std::endl;
for(int i=0; i<size; i++)
{
std::cout<<array[i]<<" ";
}
std::cout<<"/n"<<std::endl;
Fastsection(array, 0, size-1);
for(int i=0; i<size; i++)
{
std::cout<<array[i]<<" ";
}
std::cout<<"/n"<<std::endl;
}
int main()
{
// pFunction = &Sort_Bubble;
// pFunction = &Sort_Selection;
// pFunction = &Sort_Insertion;
// pFunction = &Sort_Shell;
// pFunction = &Sort_Merge;
pFunction = &Sort_Fast;
pFunction(MyArray,int(sizeof(MyArray)/sizeof(MyArray[0])));
return 0;
}
堆排序
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
算法描述
- 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
- 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
- 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
二叉堆是一颗完全二叉树,且堆中某个节点的值总是不大于其父节点的值;该完全二叉树的深度为k,除第k层外,其余各层(1~ k-1)的节点数都达到最大的个数,第k层所有的节点都连续集中在最左侧;
大顶堆:根节点数值最大;
parent(i) = i/2(取整)
left child(i) = 2i
right child(i) = 2i +1
代码实现
#include <iostream>
#include <cmath>
int MyArray[] = {8,7,1,0,9,4,3,5,6,2};
void (*pFunction)(int array[], int size);
//堆排序
//大顶堆:父亲都比儿子的数值大;
//小顶堆:儿子都比父亲大
//完全二叉树的概念
void myswap(int array[], int a, int b)
{
int temp = array[a];
array[a]= array[b];
array[b]= temp;
}
void heapAdjust(int array[], int len ,int index)
{
//叶子节点
int left = 2*index ;
int right= 2*index+1 ;
int maxIndex= index;
if(left < len && array[left] > array[maxIndex])
{
maxIndex = left;
// std::cout<<"array left = "<<array[left]<<std::endl;
}
if(right <len && array[right] > array[maxIndex])
{
maxIndex = right;
// std::cout<<"array right = "<<array[right]<<std::endl;
}
if(maxIndex != index)
{
std::swap(array[maxIndex],array[index]);
heapAdjust(array, len, maxIndex);
}
for(int i=0; i<len; i++)
{
std::cout<<array[i]<<" ";
}
std::cout<<"/n"<<std::endl;
}
void HeapSort(int array[], int size)
{
for(size_t i= size/2; i>=1; i--)
{
heapAdjust(array, size,i);
}
for(size_t i= size-1; i>=1; i--)
{
std::swap(array[0],array[i]);
heapAdjust(array,i,0);
}
}
void Sort_Heap(int array[], int size)
{
std::cout<<"array size = "<<size<<std::endl;
for(int i=0; i<size; i++)
{
std::cout<<array[i]<<" ";
}
std::cout<<"/n"<<std::endl;
HeapSort(array, size);
for(int i=0; i<size; i++)
{
std::cout<<array[i]<<" ";
}
std::cout<<"/n"<<std::endl;
}
int main()
{
// pFunction = &Sort_Bubble;
// pFunction = &Sort_Selection;
// pFunction = &Sort_Insertion;
// pFunction = &Sort_Shell;
// pFunction = &Sort_Merge;
// pFunction = &Sort_Fast;
pFunction = &Sort_Heap;
pFunction(MyArray,int(sizeof(MyArray)/sizeof(MyArray[0])));
return 0;
}
``
结果记录
```shell
array size = 10
8 7 1 0 9 4 3 5 6 2 /n
8 7 1 0 9 4 3 5 6 2 /n
8 7 1 0 9 4 3 5 6 2 /n
8 7 1 5 9 4 3 0 6 2 /n
8 7 1 5 9 4 3 0 6 2 /n
8 7 9 5 6 4 3 0 1 2 /n
8 7 9 5 6 4 3 0 1 2 /n
8 7 9 5 6 4 3 0 1 2 /n
8 9 7 5 6 4 3 0 1 2 /n
8 9 7 5 6 4 3 0 1 2 /n
9 7 6 5 2 4 3 0 1 /n
9 7 6 5 2 4 3 0 1 /n
9 7 6 5 2 4 3 0 1 /n
9 7 6 5 2 4 3 0 1 /n
7 6 4 5 2 1 3 0 /n
7 6 4 5 2 1 3 0 /n
7 6 4 5 2 1 3 0 /n
7 6 4 5 2 1 3 0 /n
6 5 4 3 2 1 0 /n
6 5 4 3 2 1 0 /n
6 5 4 3 2 1 0 /n
6 5 4 3 2 1 0 /n
5 4 2 3 0 1 /n
5 4 2 3 0 1 /n
5 4 2 3 0 1 /n
5 4 2 3 0 1 /n
4 3 2 1 0 /n
4 3 2 1 0 /n
4 3 2 1 0 /n
3 2 0 1 /n
3 2 0 1 /n
3 2 0 1 /n
2 1 0 /n
2 1 0 /n
1 0 /n
1 0 /n
0 /n
0 1 2 3 4 5 6 7 9 8 /n
[Done] exited with code=0 in 4.586 seconds