数据结构&算法
1.数据结构的存储一般常用的有几种?各有什么特点?
在计算机中,数据的存储结构可以采取如下四中方法来表现。
顺序存储方式
简单的说,顺序存储方式就是在一块连续的存储区域 一个接着一个的存放数据。顺序存储方式把逻辑上相连的结点存储在物理位置上相邻的存储单元里,结点间的逻辑关系由存储单元的邻接挂安息来体现。顺序存储方式也称为顺序存储结构(sequentialstorage structure),一般采用数组或者结构数组来描述。 线性存储方式主要用于线性逻辑结构的数据存放,而对于图和树等非线性逻辑结构则不适用。
链接存储方式
链接存储方式比较灵活,其不要求逻辑上相邻的结点在物理位置上相邻,结点间的逻辑关系由附加的引用字段表示。一个结点的引用字段往往指导下一个结点的存放位置。 链接存储方式也称为链接式存储结构(LinkedStorage Structure),一般在原数据项中增加应用类型来表示结点之间的位置关系。
索引存储方式
索引存储方式是采用附加索引表的方式来存储结点信息的一种存储方式。索引表由若干个索引项组成。索引存储方式中索引项的一般形式为:(关键字、地址)。其中,关键字是能够唯一标识一个结点的数据项。
索引存储方式还可以细分为如下两类:
稠密索引(Dense Index):这种方式中每个结点在索引表中都有一个索引项。其中,索引项的地址指示结点所在的的存储位置;
稀疏索引(Spare Index):这种方式中一组结点在索引表中只对应一个索引项。其中,索引项的地址指示一组结点的起始存储位置。
散列存储方式
散列存储方式是根据结点的关键字直接计算出该结点的存储地址的一种存储的方式。 在实际应用中,往往需要根据具体数据结构来决定采用哪一种存储方式。同一逻辑结构采用不同额存储方法,可以得到不同的存储结构。而且这四种节本存储方法,既可以单独使用,也可以组合起来对数据结构进行存储描述。
2.集合结构 线性结构 树形结构 图形结构 3.单向链表 双向链表 循环链表 4.数组和链表区别 5.堆、栈和队列
数据结构初探这里面介绍了很多数据结构,大家还可以自行查阅更多资料
冒泡排序、选择排序、插入排序、希尔排序、快速排序、归并排序、堆排序
插入排序
时间复杂度为O(n^2)
它的基本操作是将一个记录插入到已排好序的有序表中,从而得到一个新的、记录数增1的有序表。举一个例子:{38,49,65,97,76,13,27}现在前四个数已经是排好序的了,对76进行插入排序,其实就是将76和前边四个值依次做比较,发现小于97 然后将97向后移将76插入。然后对13进行判断,13 小于38 插入到下标为0的位置,然后后边的再次向后移。时间复杂度为O(n^2)。
void InsertSort(int arr[], int length) {
for (int i = 1; i < length; i++) {
int j;
if (arr[i] < arr[i - 1]) {
int temp = arr[i];
for (j = i - 1; j >= 0 && temp < arr[j]; j——) {
arr[j + 1] = arr[j];
}
arr[j + 1] = temp;
}
}
}
希尔排序
时间复杂度为O(n*d)
它其实是对直接插入排序的一种优化,它的基本思想是:先将整个待排序记录序列分割成若干个子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。因为对于直接插入排序来说,待排序的数列“基本有序”,或者 待排序的数量 N 比较小的时候,直接插入排序相对来说是比较快的时间复杂度最好的情况为O(n),那希尔排序就是从这两方面对直接排序进行的一种优化。
这里需要注意希尔排序的分割的一个特点:子序列的构成不是逐段分割而是将相隔某个“增量”的记录组成一个子序列。举个例子 {R1,R2,R3,R4,5,R6,R7,R8,R9,R10 },对这10个数进行排序。
先对{R1,R6},{R2,R7},{R3,R8},{R4,R9},{R5,R10},5个子序列进行直接插入排序。然后对{R1,R4,R7,R10},{R2,R5,R8},{R3,R6,R9},3个子序列进行直接插入排序。然后对整个序列进行直接插入排序。
但其实因为数学上存在的一些未解决的难题,用的并不多。主要是增量的取值。
快速排序和冒泡排序
二者其实都是借助交换这个概念进行排序的方法,这样不需额外开辟空间
冒泡排序的思想:先将R1和R2进行比较,如果R1>R2,那么交换R1和R2的位置,以此类推,直到比较到Rn-1 和Rn,这样第一次冒泡排序就结束了,目的是将最大的数放到了最后面,然后对剩下的n-1个数在进行这样的操作就把次大的数放在了n-1的位置。以此类推。这就好像是向海里扔了一个石头,比较小的像冒泡一样上浮,比较大的数像石头一样下沉。
时间复杂度为O(n^2)
void BubbleSort(int arr[], int length) {
for (int i = 0; i < length; i++) {
for (int j = 0; j < length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp;
temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
}
}
快速排序其实是冒泡排序的一种改进。它的基本思想是通过一趟排序将待排记录分割成两个独立的部分,其中一部分记录最小的值要大于另外一部分最大的值。,然后对这两个部分继续进行快速排序。
具体做法为:设置两个指针low和high分别指向待排序列的开始和结尾,记录下基准值baseval(待排序列的第一个记录),然后先从high所指的位置向前搜索直到找到一个小于baseval的记录并互相交换,接着从low所指向的位置向后搜索直到找到一个大于baseval的记录并互相交换,重复这两个步骤直到low=high为止。
平均时间复杂度为O(nlogn),最坏情况是O(n^2)
void QuickSort(int arr[],int left,int right)
{
if (left >= right)
return;
int baseval = arr[left];
int pLeft = left;
int pRight = right;
while (pLeft != pRight)
{
while (pLeft<pRight && arr[pRight] > baseval)
pRight--;
if (pLeft < pRight)
{
arr[pLeft] = arr[pRight];
pLeft++;
}
while (pLeft<pRight && arr[pLeft] < baseval)
pLeft++;
if (pLeft < pRight)
{
arr[pRight] = arr[pLeft];
pRight--;
}
}
arr[pLeft] = baseval;
QuickSort(arr,left,pLeft-1);
QuickSort(arr,pLeft+1,right);
}
选择排序
时间复杂度为O(n^2)
具体来说,假设长度为n的数组arr,要按照从小到大排序,那么先从n个数字中找到最小值min1,如果最小值min1的位置不在数组的最左端(也就是min1不等于arr[0]),则将最小值min1和arr[0]交换,接着在剩下的n-1个数字中找到最小值min2,如果最小值min2不等于arr[1],则交换这两个数字,依次类推,直到数组arr有序排列。
void SelectionSort (int arr[],int length) {
for(int i = 0;i < length;i++) {
int index = i;
for (int j = i+1;j<length;i++) {
if (arr[j] < arr[index])
index = j;
}
if(index == i)
continue;
else {
int temp = arr[index];
arr[index] = arr[i];
arr[i] = temp;
}
}
}
堆排序 图解
时间复杂度为O(nlogn)
堆的定义如下:n个元素的序列{k1,k2,….,kn}当且仅当满足以下条件时称之为堆
可以将堆看做是一个完全二叉树,并且每个节点的值都大于等于其左右孩子节点的值,称为大顶堆;或者小于等于其左右孩子节点的值,称为小顶堆。
堆排序就是利用堆进行排序的方法。基本思想为:
将待排序列构造成一个大(小)顶堆,整个序列的最大(小)值就是堆顶的根节点;
将根节点的值和堆数组的末尾元素交换,末尾原始就是最大(小)值,
然后将剩余的n-1个序列重新构造成一个堆,反复执行,最终得到一个有序序列。
void swap(int arr[],int a,int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
//调整大顶堆
void adjustHeap(int arr[],int i,int length) {
int temp = arr[i];
for(int k = i*2 +1;k<length;k=k*2+1){ //从i节点的左子节点开始就是2i+1
if(k+1<length && arr[k]<arr[k+1]) { //如果左子节点小于右子节点,k指向右子节点
k++;
}
if(arr[k] > temp) { //如果子节点大于父节点,将子节点赋值给父节点(不用交换)
arr[i] = arr[k];
i = k;
}else {
break;
}
}
arr[i] = temp;
}
void heapSort(int arr[],int length) {
//1.构建大顶堆
for(int i = length/2-1; i>0; i--){
adjustHeap(arr,i,length);//从第一个非叶子节点从下至上,从右向左调整结构
}
//2.调整堆结构 交换堆顶元素与末尾元素
for(int j=length-1;j>0;j--) {
swap(arr,0,j);
adjustHeap(arr,0,j);//重新对堆进行调整,因为除了0是因为交换后改变了,其他的排序没有改变,所以需要从堆顶的那个值调整
}
}
归并排序
时间复杂度为O(nlogn)
归并的含义是将两个或两个以上的有序表组合成一个新的有序表。
假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列长度为1,然后两两归并,得到n/2个长度为2或者1的有序子序列,然后再两两归并。如此重复,直至得到一个长度为n的有序序列为止,这就是2-路归并排序。
//temp是一个与原数组等长度的临时数组。
void Sort(int arr[],int start ,int end,int *temp) {
if(start >= end) return;
int mid = (start + end)/2;
Sort(arr,start,mid,temp);
Sort(arr,mid+1,end,temp);
Merge(arr,start,mid,end,temp);
}
void Merge (int arr[],int start,int mid,int end,int *temp) {
int i = start;//左边序列最前边的指针
int j = mid + 1;//右边序列的最前边的指针
int t = 0; //临时数组指针
while (i <= mid && j <= end) {
if(arr[i] <= arr[j]) {
//temp[t] = arr[i];
//t++;i++;
temp[t++] = arr[i++]; 上边两行等于这一行
}else{
temp[t++] = arr[j++];
}
}
while (i<=mid) {
temp[t++] = arr[i++];
}
while (j<=end) {
temp[t++] = arr[j++];
}
t=0;
while (start <= end) {
arr[start++] = temp[t++];
}
}