数据结构--线性表之数组

基础不牢地动山摇,尽管曾经我也是基础牢的一份子,但是这么长时间的洗礼,呵呵,几乎忘干净了。

为什么要数据结构呢?干什么用的呢?有什么好处呢?

有人说数据结构等于数据加算法。对数据的应用。其实编程来讲一切就是数据,,你写一个界面,界面展现出来的那些东西不过就是一堆数据经过各种加工,加工成另外一种对应屏幕的数据,再根据这波数据展现到屏幕上而已。说到底就是对 0 和 1 的操作。但是咱们平常善于用的是10进制,而非二进制。所以带来了转化问题,如何用0101代表 10 呢,代表1.1呢?代表-1呢?当然这是另外一门科学。数据结构呢,就是怎么组织这一类的数据尤其是应用范围及其广泛的特殊结构数据,例如全班同学的名字,,名字也是一种数据吧,全班来讲的话那得有很多同学的名字吧。我该怎么存?怎么管理呢?那我存的时候,涉及到的一些操作,增删查改,如何在内存这块死物上做到速度最快,占用内存最少的增删查改呢??我想列出明朝朱元璋家族的族谱,该以什么样的方式存储呢,并且遍历的时候有什么方法啊?朱棣和朱允炆的关系啊?内存中怎么分布怎么描述这类的东西呢,毕竟古代的皇帝不只有一个儿子。我感觉数据结构就是把各种生活中的关系抽象出来,再组成特别的结构来展现这种关系,而这种关系一旦抽象出来并且正确的话,就能描述好多事物解决好多问题。比如集合,实则是一种关系,关系的展现之处是有共同点。比如大润发超市的所有商品,共同点是在大润发。。三年二班的同学,共同点就是都在三年二班。。。还有的关系比如继承啊,感觉树,,各种爸爸孩子的。。

数据结构的线性表,顾名思义,是呈线性的,但是在数据结构的存储方面又将线性表分为了数组,和链表。区别呢,是数组元素的存储方式,是在内存里面开了一段空间并且元素之间的地址位置是连续的。而链表是爱存哪儿存哪儿,我们只需要在存的元素中标出下一个元素的地理位置,顺藤摸瓜,反正也是一条线的,也是把数据存储下来了。。这就是数组和线性表之间的本质区别。正因这样的本质区别,,也导致了二者之间上的某些差异。比如二者在增加删除一个元素的区别导致的差异,,插入,查找元素区别导致的差异。

先看数组:

数据结构中数组的理论:记住一句,对于增删查改,最终的结果都是导向他的概念就是了,一段连续的存储空间。连续二字非常重要。

数组是这样存的 :


既然是一段存储空间,,还是连续的存储空间。。那么刚开始的时候给这个数组开多大空间,并不是说开多少就多少的。开太大用不了是浪费,开太小撑不开元素也不太合适。所以咱们用的时候,一般都有 int [] i = new int[6];之类的写法。好歹也得说一声,我到底要多大。

数组的增:很简单,在尾部加一个即可。

数据的插入:当线性表是空的时候,直接插入到头部,也就是0的位置上即可

                    当插入到尾部的时候,直接在尾部加一个即可。

                  重点看下平常的插入,从中间插入。

            

数据的删除:

如果是从末尾删除的话,直接从最后一个删就是了,也不设置位置的倒腾这类事情。


尝试用顺序表实现排序算法(持续更新..)

排序方面:

冒泡排序,选择排序,插入排序,归并排序等等。

所有的排序方法分类如下:

交换排序:

1) 冒泡排序 
2) 快速排序

选择排序:

3) 直接选择排序 
4) 堆排序

插入排序:

5) 直接插入排序 
6) 希尔排序

合并排序:

7) 合并排序


1 冒泡排序:

思路大概是这样的,我可以通过交换相邻元素的方法,对相信的元素进行对比,谁大或者谁小,假设由小到大排的话,就把大的数排到这两个数里相对右面的位置,一般是通过这俩数交换元素得到的。然后继续循环控制,往后滚,继续比较。这样一趟循环下来,就会发现最大数用不着多少次就滚到最右边了。。这样一趟就完成。接下来几趟依旧这样走!慢慢的,发现大的渐渐滚到右边了,顺序当然就是从小到大了。例子:

6,5,4,3,8

开始:那就慢慢滚呗,当然官方叫冒泡。

首先第一个元素和第二个元素比较, 那就是6 和 5 呗,,咦~ 6>5 好吧,6与5交换,,变成了 5,6,4,3,8  一次比较完成

接下来往后滚,到了第二个元素和第三个元素比较了,反正目前为止第二个元素绝逼是已经比较过的里面最大的,,6 和 4,咦~6还是大的,那么不好意思哈,咱俩交换下位置吧,我得在右边。。于是乎 第二次比较完成之后是这样的,,5,4,6,3,8

到了这里是不是发现啥规律了,就是6就像一个泡泡一样往后窜,但是只是它窜了,并没有影响其他元素的大致顺序。接下来再按着这个路子走,,就是 5,4,3,6,8          然后再比,就是5,4,3,6,8  因为此时的的确确6大不过8的,,所以喽乖乖排到这里了。发现了没,6就这样滚到后面去了。  

一趟排序就此完成!

接下来就是第二趟呗,老规矩,还是从第一个元素来, 目前是从5开始了吧,,就按着相邻元素取最大,并且再拿他往后继续比这路子,,慢慢的老三就会被顶到后面去。

不出几趟排序,从小到大就完成了。

代码实现:

原始方法:

/**
	 * 计算复杂度是o(n2) 是一种不太快的排序方法
	 * 详细计算实则是算了 n*n*3 + 1 不连if语句的话。
	 * 可想而知,并不是一种很好的排序方法。
	 * @param l
	 */
	void maopao1 (int [] l) {
		for (int i = 0; i < l.length -1; i ++) {
			for (int j = 0; j < l.length - 1; j ++) {
				if (l[j] > l[j+1]) {
					int temp = l[j];
					l[j] = l[j+1];
					l[j+1] = temp;
				}
			}
		}
		
		printArray(l, "冒泡排序1排完了");
	}

被优化之后:

	/**
	 * 被优化的冒泡排序,复杂度为之前的一半。
	 * O(1/2n^2)但是吧,还是不快!
	 * @param l
	 */
	void maopao2 (int[] l){
		for (int i = 0; i < l.length - 1; i ++){
			for(int j = 0; j + 1 < l.length - i; j++) {
				if (l[j] > l[j+1]){
					int temp = l[j];
					l[j] = l[j+1];
					l[j+1] = temp;
				}
			}
		}
		
		printArray(l, "冒泡方法2排完了");
	}

2 快速排序:

这个过程比较那个啥,,这个真的不用笔画一下很难自己把画面展现出来。建议百度一下,然后拿笔画一画。

其实就是一个不断找指定数应该在一段区域里面的位置,找到之后,再根据这个数应该在的位置将原来所在的整体一分为二,再走这样的路子。就这样越分越小,,递归没几下,所有数字就能在它应该在的位置了。那么如何找这个位置呢??建议还是百度一下吧,毕竟我连话说不清楚有时候。那说下怎么找吧。。首先一个数组,它会把第一位元素的值指定为 vol 值,,这个值拿出来,就是来做比较用的。然后还要有一个low, 和 high , 分别表示第一位元素的指针位置和最后一个元素的指针位置。。以后,就是让他们不断根据规则变位置,来取值和vol比较用的。

那么首先呢,会先取high位置指向的值与vol来比较,如果,high这个值大于vol值,那就啥也不做了,high--,把地址换到前一个元素的,再取出和vol比较,如果此时还是大于vol值那就再啥也不干,依旧把指针往前滑,,直到碰到它的值与vol比较,小于vol 的时候,就要开始有真动作了,将此时的这个值,与当前low指针指向的值,,两个值,互换,,注意是值互换!!也就是把芯抠出来互调了,那么high指针目前为止,该他轮完了,,值都换了,这时候重点就跑到low上了,别忘了,此时low所代表的值,就是我们刚刚互调过来的,,此时的值小于vol是必然的,所以我们不用管,,直接low++, 往前滑一个,如果此时low所指向的值小于vol的话,甭管了,继续往前滑,,直到碰到low指向的值比vol大了!!!这时,把low上的值和当前high指向的值,互换!!换值!换完之后,接着,周而复始老套路,轮到high来比较滑动了。。接下来就是这规则。反正到最后呢,你就会发现low 和 high 的指针是离得越来越近,,慢慢的,vol 这个值,,可是最开始的第一个元素的值呀!!调来调去,,成了它左边的数都比它小,右边的数都比它大了。那这样的话,起码它呆的位置就是最终排完之后的位置毫无疑问!!!。这样一趟快速排序完成。接下来呢,,就以初始值排到的这个位置,为分割线,,左边再按照这样的规则排,右边同理!递归排去吧。没几下,就排完了!

上代码,先上一份我写的,只看了一些思路没有别人例子的情况下写的,再垃圾我也乐意

/**
	 * 这是我根据看快排的思路自己写的,哎,屎一样的编码能力,,估计应该不是最优方案。
	 * 但还是很开心哈哈哈哈。
	 * @param i
	 * @param low
	 * @param high
	 */
	void wodeKuaipai(int[]i, int low, int high){
//		System.out.println("函数执行开始 low = " + low + ", high = " + high);
		int vol = i[low];
		
		
		printArray(i, "函数开始数组形态");
		
		if (high <= low || low + 1 == high) {
			if (i[low] > i[high]){
				int temp = i[high];
				i[high] = i[low];
				i[low] = i[high];
			}
			return;
		}
		int beginlow = low;
		int beginHigh = high;
		
		while (high > low) {
			if (i[high] < vol) {
				int temp = i[high];
				i[high] = i[low];
				i[low] = temp;
				low ++;
				
				while (high > low) {
					if (i[low] > vol){
						int temp2 = i[low];
						i[low] = i[high];
						i[high] = temp2;
						high -- ;
						break;
					}else{
						low ++;
					}
				}
			}else{
				high --;
			}
		}
		if (vol == i[low]){
			if (low - 1 > 0) {
				wodeKuaipai(i, beginlow, low -1);
			}
			if (low + 1 < i.length){
				wodeKuaipai(i, low + 1, beginHigh);
			}
		} else if (vol == i[high]) {
			if (high - 1 > 0) {
				wodeKuaipai(i, beginlow, high -1);
			}
			if (high + 1 < i.length) {
				wodeKuaipai(i, high + 1, beginHigh);
			}
		}	
	}

运行结果,第一行是最开始的形态:



好吧,看一下专业的代码时如何写的:嗷嗷,我明白了枢纽的意思了,,,,原来还有枢纽这一说

 /// <summary>
        /// 快速排序算法
        /// </summary>
        /// <param name="list"></param>
        /// <param name="low"></param>
        /// <param name="high"></param>
        public static void QuickSort(List<int> list, int low, int high)
        {
            if (low < high)
            {
                //分割数组,找到枢轴
                int pivot = Partition(list,low,high);

                //递归调用,对低子表进行排序
                QuickSort(list,low,pivot-1);
                //对高子表进行排序
                QuickSort(list,pivot+1,high);
            }
        }

        /// <summary>
        /// 分割列表,找到枢轴
        /// </summary>
        /// <param name="list"></param>
        /// <param name="low"></param>
        /// <param name="high"></param>
        /// <returns></returns>
        private static int Partition(List<int> list, int low, int high)
        {
            //用列表的第一个记录作枢轴记录
            int pivotKey = list[low];

            while (low < high)
            {
                while (low < high && list[high] >= pivotKey)
                    high--;
                Swap(list,low,high);//交换

                while (low < high && list[low] <= pivotKey)
                    low++;
                Swap(list,low,high);
            }
            //返回枢轴所在位置
            return low;
        }

        /// <summary>
        /// 交换列表中两个位置的元素
        /// </summary>
        /// <param name="list"></param>
        /// <param name="low"></param>
        /// <param name="high"></param>
        /// <returns></returns>
        private static void Swap(List<int> list, int low, int high)
        {
            int temp = -1;
            if (list != null && list.Count > 0)
            {
                temp = list[low];
                list[low] = list[high];
                list[high] = temp;
            }
        }
    }
}

然后感觉快速排序把,当要排的元素数量越多,相比于冒泡模式就会越快。冒泡模式可是O(n^2)的复杂度,,但是快速排序的复杂度虽然我不知道怎么算但是复杂度绝对没有前者高!

之前的代码改进一下:这下估计差不多了

void wodeKuaipai(int[]i, int low, int high){
		System.out.println("函数执行开始 low = " + low + ", high = " + high);
		printArray(i, "函数开始数组形态");
		
		if (low >= high) {
			return;
		}
		int vol = i[low];
		int beginlow = low;
		int beginHigh = high;
		
		while (high > low) {
			if (i[high] < vol) {
				int temp = i[high];
				i[high] = i[low];
				i[low] = temp;
				low ++;
				while (high > low) {
					if (i[low] > vol){
						int temp2 = i[low];
						i[low] = i[high];
						i[high] = temp2;
						high--;
						break;
					}else{
						low ++;	
					}
				}
			}else{
				high --;
			}		
		}
		System.out.println( "high = " + high + " low = " + low);
		wodeKuaipai(i, beginlow, low -1);
		wodeKuaipai(i, low + 1, beginHigh);	
	}

3 插入排序:

面试中的排序算法总结

插入排序不是通过交换位置而是通过比较找到合适的位置插入元素来达到排序的目的的。相信大家都有过打扑克牌的经历,特别是牌数较大的。在分牌时可能要整理自己的牌,牌多的时候怎么整理呢?就是拿到一张牌,找到一个合适的位置插入。这个原理其实和插入排序是一样的。举个栗子,对5,3,8,6,4这个无序序列进行简单插入排序,首先假设第一个数的位置时正确的,想一下在拿到第一张牌的时候,没必要整理。然后3要插到5前面,把5后移一位,变成3,5,8,6,4.想一下整理牌的时候应该也是这样吧。然后8不用动,6插在8前面,8后移一位,4插在5前面,从5开始都向后移一位。注意在插入一个数的时候要保证这个数前面的数已经有序。简单插入排序的时间复杂度也是O(n^2)。

我的例子:

void charuPaixu(int[] l) {
		printArray(l, "插入排序原始数据。");
		for (int i = 1; i < l.length; i ++) {
			int p = i;
			for (int j = p - 1; j >= 0; j--) {
				if (l[p] < l[j]) { //开始调换, 把i指向的元素抽出来,然后后面的数往后移动,最后把这个元素插入应在的位置。
					int num = l[p];
					while (p > j) {
						l[p] = l[p - 1];
						p --;
					}
					l[j] = num;
					printArray(l, "里层置换");
				}
			}
		}		
		printArray(l, "我的插入排序");
	}

正确答案:

代码行数少,干净整洁。

void charuPaixu(int[] l) {
		printArray(l, "插入排序原始数据。");
		for (int i = 1; i < l.length; i ++) {
			int j = i;
			int target = l[i];
			while (j > 0 && target < l[j - 1]){
				l[j] = l[j-1];
				j--;
			}
			l[j] = target;
		}		
		printArray(l, "我的插入排序");
	}
为什么我总是写复杂呢。。。 大哭


4 希尔排序

于我而言相当复杂啊。。。反正这个我是真的无法用言语描述了,复制粘贴大法:

 希尔排序是插入排序的一种高效率的实现,也叫缩小增量排序。简单的插入排序中,如果待排序列是正序时,时间复杂度是O(n),如果序列是基本有序的,使用直接插入排序效率就非常高。希尔排序就利用了这个特点。基本思想是:先将整个待排记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录基本有序时再对全体记录进行一次直接插入排序。

举个栗子:

 

   从上述排序过程可见,希尔排序的特点是,子序列的构成不是简单的逐段分割,而是将某个相隔某个增量的记录组成一个子序列。如上面的例子,第一堂排序时的增量为5,第二趟排序的增量为3。由于前两趟的插入排序中记录的关键字是和同一子序列中的前一个记录的关键字进行比较,因此关键字较小的记录就不是一步一步地向前挪动,而是跳跃式地往前移,从而使得进行最后一趟排序时,整个序列已经做到基本有序,只要作记录的少量比较和移动即可。因此希尔排序的效率要比直接插入排序高。

  希尔排序的分析是复杂的,时间复杂度是所取增量的函数,这涉及一些数学上的难题。但是在大量实验的基础上推出当n在某个范围内时,时间复杂度可以达到O(n^1.3)。

但是呢,希尔排序相比于插入排序的确快!!而且数越多,差距就越现出来了。
/**
	 * 采用增量,来给元素分块,每个增量的一个元素作为同一类进行插入排序,,增量的大小就是数组长度不断/2 的结果
	 * 这样分着分着就越分越细。。没几次就拍出来了。   
	 * @param list
	 */
    void myshellSort(ArrayList list) {
    	if (list == null || list.size() == 1) {
    		return;
    	}
    	int d = list.size() / 2;
    	while (d >= 1) {
    		shellInsert(list, d);
    		d = d/2;
    	}
    	System.out.println("我的希尔排序:" + list.toString());
    }
    
    /**
     * 
     * @param list 
     * @param d	增量
     */
    void shellInsert(ArrayList<Integer> list, int d){
    	System.out.println("d = " + d);
//    	System.out.println("希尔排序初始位置" + list.toString());
    	int temp;
    	int j;
    	for (int i = d; i < list.size(); i ++) {
    		temp = list.get(i);
    		j = i;
    		while(j - d >= 0 && list.get(j-d) > temp) {
	    		list.set(j, list.get(j-d)); //向后挪
	    		j -= d;
	    	}
    		list.set(j, temp);
    	}
    }
排序方法还有好多,目前先整理这些吧。这是几个比较常见的排序方法。





















猜你喜欢

转载自blog.csdn.net/weixin_28774815/article/details/80615275
今日推荐