数组

163 阅读5分钟

基本概念

数组(Array)是一种线性表(Liner List)数据结构,它用一组连续的内存空间,来存储一组具有相同类型的数据。

特点

连续的内存空间和相同的数据类型,因为这两个限制,数组拥有随机访问的特性,根据索引访问元素的时间复杂度是O(1)。

但是这两个限制让数组的很多操作变得非常低效,比如数组的删除和插入操作,为了保证连续性,就需要大量的数据复制和搬运操作。

寻址公式:

a[i]_address = base_address + i * data_type_size

数组的java语言实现<java.util.ArrayList>

 public class ArrayList<E> extends AbstractList<E>
      implements List<E>, RandomAccess, Cloneable, Serializable
  {
         /**
         * The default capacity for new ArrayLists.
         */
         private static final int DEFAULT_CAPACITY = 10;
         /**
         * Where the data is stored.
         */
         private transient E[] data;
         /**
         * Construct a new ArrayList with the supplied initial      capacity.
         *
         * @param capacity initial capacity of this ArrayList
         * @throws IllegalArgumentException if capacity is negative
         */
           public ArrayList(int capacity)
           {
             // Must explicitly check, to get correct exception.
             if (capacity < 0)
               throw new IllegalArgumentException();
             data = (E[]) new Object[capacity];
           }
           /**
            * Construct a new ArrayList with the default capacity (16).
            * 使用默认容量构造新的ArrayList(16)
            */
           public ArrayList()
         {
             this(DEFAULT_CAPACITY);
         }
          /**
           * Guarantees that this list will have at least enough capacity to
           * hold minCapacity elements. This implementation will grow the list to
           * max(current * 2, minCapacity) if (minCapacity &gt; current). The JCL says
           * explictly that "this method increases its capacity to minCap", while
           * the JDK 1.3 online docs specify that the list will grow to at l east the
           * size specified.
           * "保证数组有足够的容量,如果不够,需要进行扩容,将容量扩大为Math.max(current * 2, minCapacity)"
           * "创建一个新的数组,然后也会进行复制搬运操作"
           * @param minCapacity the minimum guaranteed capacity
           */
           public void ensureCapacity(int minCapacity)
         {
            int current = data.length;
             if (minCapacity > current)
           {
              E[] newData = (E[]) new Object[Math.max(current * 2, minCapacity)];
              System.arraycopy(data, 0, newData, 0, size);
              data = newData;
           }
         } 
         /**
          * Returns true iff element is in this ArrayList.
          *
          * @param e the element whose inclusion in the List is being tested
          * @return true if the list contains e
          */
          public boolean contains(Object e)
         {
            return indexOf(e) != -1;
         }
          /**
         * Returns the lowest index at which element appears in this List, or
         * -1 if it does not appear.
         * "看来查询一个数组是否包含某一个值,也就是执行一次O(n)的for循环遍历"
         * @param e the element whose inclusion in the List is being tested
         * @return the index where e was found
         */
         public int indexOf(Object e)
         {
           for (int i = 0; i < size; i++)
                if (equals(e, data[i]))
                return i;
           return -1;
         }
         
         /**
          * Retrieves the element at the user-supplied index.
          *
          * @param index the index of the element we are fetching
          * @throws IndexOutOfBoundsException if index &lt; 0 || index &gt;= size()
          */
         public E get(int index)
         {
               "//首先判断索引是否越界"
               checkBoundExclusive(index);
               return data[index];
         }
 
         /**
          * Sets the element at the specified index.  The new element, e,
          * can be an object of any type or null.
          *
          * @param index the index at which the element is being set
          * @param e the element to be set
          * @return the element previously at the specified index
          * @throws IndexOutOfBoundsException if index &lt; 0 || index &gt;= 0
          */
         public E set(int index, E e)
         {
               "//首先判断索引是否越界"
               checkBoundExclusive(index);
               E result = data[index];
               data[index] = e;
               return result;
         }
         
         
        /**
         * Appends the supplied element to the end of this list.
         * The element, e, can be an object of any type or null.
         * "添加元素到数组的末尾。如果当前的size等于数组的长度,就需要进行扩容(复制和搬运)操作"
         * Tips: i = 2 ; i++ = 2  ++i = 3 而 i = 3
         * @param e the element to be appended to this list
         * @return true, the add will always succeed
         */
        public boolean add(E e)
        {
            modCount++;
            if (size == data.length)
                ensureCapacity(size + 1);
            data[size++] = e;
            return true;
        }
        /**
         * Adds the supplied element at the specified index, shifting all
         * elements currently at that index or higher one to the right.
         * The element, e, can be an object of any type or null.
         * "在指定索引处添加提供的元素,将当前位于该索引处或更高索引处的所有元素向右移动"
         * @param index the index at which the element is being added
         * @param e the item being added
         * @throws IndexOutOfBoundsException if index &lt; 0 || index &gt; size()
         */
        public void add(int index, E e)
        {
            checkBoundInclusive(index);
            modCount++;
            if (size == data.length)
                ensureCapacity(size + 1);
            if (index != size)
                "index并不是末尾的索引,需要将从index到size的元素复制搬运到index+1的位置,元素的个数即长度为size - index"
                "时间复杂度为O(n)"
                System.arraycopy(data, index, data, index + 1, size - index);
            data[index] = e;
            size++;
        }
        /**
         * Removes the element at the user-supplied index.
         * "删除指定数组中指定索引的元素"。
         * @param index the index of the element to be removed
         * @return the removed Object
         * @throws IndexOutOfBoundsException if index &lt; 0 || index &gt;= size()
         */
        public E remove(int index)
        {
            "//检查索引是否越界"
            checkBoundExclusive(index);
            E r = data[index];
            modCount++;
            if (index != --size)
                "index指定的元素不是末尾元素。需要将index + 1到size的元素复制搬运到index的位置,元素的个数即长度为size - index"
                "时间复杂度为O(n)"
                System.arraycopy(data, index + 1, data, index, size - index);
            // Aid for garbage collection by releasing this pointer.
            data[size] = null;
            return r;
        }

实战题目

移动零LeetCode链接

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:

输入: [0,1,0,3,12] 输出: [1,3,12,0,0]

说明: 必须在原数组上操作,不能拷贝额外的数组。 尽量减少操作次数。

Tips: 在对数组进行操作的时候,尽最大可能不要对数组进行插入和删除操作。从数组源码上看,插入和删除操作内部会执行复制搬运操作,时间复杂度是O(n)。

方法1

用到了数组的删除方法,内部时间复杂度是O(n),外部又有for遍历时间复杂度为O(n),所以此方案的时间复杂度是O(n^2)

class Solution:
    def moveZeroes(self, nums: List[int]) -> List[int]:
        deleteNum = 0
        for num in nums[:]:
            if num == 0:
                //用到了数组的删除方法,时间复杂度是O(n)
                nums.remove(num)
                deleteNum += 1
        for i in range(deleteNum):
            nums.append(0)
        return nums

方法2

通过双指针法,空间换取时间的方法,细分的话,我起了一个名字叫同向双指针法,当两个指针index与i出现不相等时候,必然是需要做特殊处理的时机,因为他们是从同一个起点,同一个方向出发的。

index记录下一个非零的索引或者说个数。当遇到元素为零的时候,待下一次遍历到非零的元素时,index和i自然不会相等,这时候将非零元素赋值给index,并index+1,将i位置的元素赋值为0,相当于交换。时间复杂度为O(1)。这样这个算法时间复杂度就为O(n)。

def moveZeroes(self, nums: List[int]) -> List[int]:
        """
        数组尽量不要做拷贝操作,使用一个删除操作,删除操作本身会拷贝数据
        """
        index = 0
        for i in range(len(nums)):
            if nums[i] != 0:
                nums[index] = nums[i]
                if i != index:  
                //这个条件如何很快想得通? 其实这也是相当于一种交换
                    nums[i] = 0
                index += 1 
        return nums